diff --git a/NEWS.md b/NEWS.md index 72ae7efe..23061573 100644 --- a/NEWS.md +++ b/NEWS.md @@ -113,6 +113,22 @@ New theme features: Note that this does imply a change for `"barplot"` types, which previously used a slightly darker `"grey"` (to match base R's `barplot()`), but we decided internal consistency was the more important feature to prioritize. +- Building on the above, _multi-group_ `"boxplot"`, `"violin"`, and `"barplot"` + displays now also use the lighter-but-opaque fill, so that grouped plots match + their single-group counterparts (previously grouped barplots used dark + saturated fills and grouped boxplots/violins used semi-transparent fills). Each + type gains a `lighten` argument (default `TRUE`); set + `type_barplot(lighten = FALSE)` (etc.) to recover the fully-saturated palette + colours. `"histogram"` is deliberately excluded, since semi-transparency + remains preferable for overlapping grouped distributions. `"spineplot"` also + gains a `lighten` argument (only relevant for the `y == by` case), but defaults + to `FALSE` (saturated fills): its tiles abut one another with no gap, so the + darker fills read better against their matching border colours. + (#646 @grantmcdermott @zeileis) + - Relatedly, transparency requested via `fill` or `alpha` is now layered _on + top of_ the (lightened) fill rather than the saturated base colour. This + fixes a counterintuitive case where adding an alpha fill (e.g. `fill = 0.7`) + could _darken_ the interior of an area-based plot. Theme fixes: diff --git a/R/by_aesthetics.R b/R/by_aesthetics.R index 2595b63f..babe936f 100755 --- a/R/by_aesthetics.R +++ b/R/by_aesthetics.R @@ -1,5 +1,5 @@ # -## orchestration function ----- +## main orchestration function ----- # by_aesthetics = function(settings) { @@ -8,7 +8,7 @@ by_aesthetics = function(settings) { environment(), c( "datapoints", "by", "type", "null_by", "pch", "bg", "lty", "lwd", - "bubble", "cex", "alpha", "col", "fill", "ribbon.alpha" + "bubble", "cex", "alpha", "col", "fill", "ribbon.alpha", "lighten" ) ) @@ -59,6 +59,7 @@ by_aesthetics = function(settings) { type = type, by = by, ribbon.alpha = ribbon.alpha, + lighten = lighten, adjustcolor = adjustcolor ) @@ -72,9 +73,293 @@ by_aesthetics = function(settings) { # -## helper functions ----- +## subsidiary functions ----- # +by_col = function(col, palette, alpha, by_ordered, by_continuous, ngrps, adjustcolor) { + ordered = if (is.null(by_ordered)) FALSE else by_ordered + gradient = if (is.null(by_continuous)) FALSE else by_continuous + assert_logical(ordered) + assert_logical(gradient) + + if (is.null(alpha)) alpha = 1 + if (gradient) ngrps = 100L + + if (is_by_keyword(col)) col = NULL + + pal_theme = if (ordered || gradient) { + get_tpar("palette.sequential", default = NULL) + } else { + get_tpar("palette.qualitative", default = NULL) + } + + # Resolve a (possibly relative) col.default against the qualitative palette. + # A relative (negative) index also drops the chosen colour from the grouped + # palette, so e.g. `col.default = -1` uses the palette's leading colour for + # single-group displays and the palette-minus-that-colour for grouped ones + # (see resolve_col_default). col.default only applies to qualitative displays. + if (!ordered && !gradient) { + cd = resolve_col_default(get_tpar("col.default", default = NULL), pal_theme) + pal_theme = cd[["palette"]] + # Single-group default colour (theme-settable via tpar). When NULL, defer to + # the usual palette resolution below (first qualitative colour, or palette()[1]). + if (is.null(col) && ngrps == 1L) { + col = cd[["col_default"]] + } + } + + cols = resolve_manual_colors(col, ngrps, gradient, ordered, alpha, adjustcolor) + if (!is.null(cols)) { + return(cols) + } + + cols = resolve_palette_colors( + palette = palette, + theme_palette = pal_theme, + ngrps = ngrps, + ordered = ordered, + gradient = gradient, + alpha = alpha, + adjustcolor = adjustcolor + ) + + cols +} + + +by_bg = function(bg, fill, col, palette, alpha, by_ordered, by_continuous, ngrps, type, by, ribbon.alpha, lighten, adjustcolor) { + lighten = if (is.null(lighten)) TRUE else isTRUE(lighten) + if (is.null(bg) && !is.null(fill)) bg = fill + + # A numeric bg/fill in [0,1] is an alpha *request*, not a colour: remember it + # as the transparency to layer on top of the resolved (and possibly lightened) + # base fill, and defer to the "by" palette for the base colour itself. Applying + # alpha last -- after lightening -- means a fill request blends a light tint + # toward the background rather than darkening a saturated base (issue #646). + if (!is.null(bg) && length(bg) == 1 && is.numeric(bg) && bg >= 0 && bg <= 1) { + alpha = bg + bg = "by" + } + + ordered = if (is.null(by_ordered)) FALSE else by_ordered + gradient = if (is.null(by_continuous)) FALSE else by_continuous + + # Whether the base fill comes from palette resolution (the "by" keyword, incl. + # a numeric fill request) or the single-group default, vs. an explicit colour + # such as `fill = "bisque"`. Only the former are eligible for lightening; an + # explicit colour is always honoured verbatim. + bg_from_palette = !is.null(bg) && length(bg) == 1 && is_by_keyword(bg) + palette_fill = bg_from_palette + + if (bg_from_palette) { + pal_theme = if (ordered || gradient) { + get_tpar("palette.sequential", default = NULL) + } else { + get_tpar("palette.qualitative", default = NULL) + } + # Mirror by_col(): a relative col.default trims the grouped palette, so + # grouped fills stay in step with grouped line colours. + if (!ordered && !gradient) { + pal_theme = resolve_col_default(get_tpar("col.default", default = NULL), pal_theme)[["palette"]] + } + # Resolve opaque (alpha = 1); any user alpha is layered on below. + bg = resolve_palette_colors( + palette = palette, + theme_palette = pal_theme, + ngrps = ngrps, + ordered = ordered, + gradient = gradient, + alpha = 1, + adjustcolor = adjustcolor + ) + } else if (ngrps == 1L && is.null(bg) && type %in% c("boxplot", "violin", "barplot", "histogram")) { + # Single-group fill tracks the theme's *default* colour (col.default -> + # palette[1] -> black) so that themed box/violin/bar/histogram plots match + # their own multi-group counterparts. We resolve this default independently + # of `col` so that a user-supplied outline colour (e.g. `col = "white"`) + # doesn't bleed into the fill. + bg = by_col( + col = NULL, palette = palette, alpha = 1, by_ordered = by_ordered, + by_continuous = by_continuous, ngrps = 1L, adjustcolor = adjustcolor + ) + palette_fill = TRUE + } else if (length(bg) != ngrps) { + bg = rep(bg, ngrps) + } + + # The solid categorical area types take the lighter-but-opaque tint, but only + # for qualitative groupings: lightening a sequential/continuous ramp would + # crush the contrast between adjacent levels. + area_light_types = c("barplot", "boxplot", "violin") + light_ok = lighten && type %in% area_light_types && !ordered && !gradient + + if (palette_fill) { + # For a *chromatic* base the fill is a lighter-but-opaque tint of that colour + # (via seq_palette, the same HCL ramp used for ridge fills and legend + # swatches), so it reads cleanly over grid lines unlike alpha blending. For + # an *achromatic* base (typically black, e.g. the "bw"/"classic"/"ipsum" + # themes and the plain default) seq_palette's light endpoint can't reach the + # neutral "lightgray" used by the no-theme path and base R's hist()/boxplot() + # -- so we fall back to that literal. Histograms only lighten in the + # single-group case; grouped histograms keep their semi-transparent fill so + # that overlapping distributions remain legible (issue #646). + if (light_ok) { + bg = lighten_fill(bg) + } else if (type == "histogram" && ngrps == 1L) { + bg = lighten_fill(bg) + } + # Layer any user-supplied transparency on top of the (lightened) base fill. + if (!is.null(alpha)) bg = apply_alpha(bg, alpha, adjustcolor) + } + + # Semi-transparent fill for ribbon-type plots (the default look for ribbons). + # Other area types only become transparent if the user explicitly asks (e.g. + # `fill = 0.3`), which is handled via the `alpha` layering above. + if (type == "ribbon") { + if (!is.null(bg)) { + bg = adjustcolor(bg, ribbon.alpha) + } else if (!is.null(col)) { + bg = adjustcolor(col, ribbon.alpha) + } + } + + bg +} + + +by_pch = function(ngrps, type, pch = NULL) { + no_pch = FALSE + if (identical(type, "text")) { + pch = rep(15, ngrps) + } else if (!type %in% c("p", "b", "o", "pointrange", "errorbar", "boxplot", "qq")) { + no_pch = TRUE + pch = NULL + + # special "by" convenience keyword + } else if (is_by_keyword(pch)) { + no_pch = TRUE # skip checks below + pch = 1:ngrps + par("pch") - 1 + # correctly recycle if over max pch type + if (max(pch) > 25L) { + pch_below = pch[pch <= 25L] + pch_above = pch[pch > 25L] + pch_above = rep_len(0:25, length(pch_above)) + pch = c(pch_below, pch_above) + } + + # return NULL if not a valid point type + } else if (is.null(pch)) { + pch = par("pch") + } + + if (!no_pch) { + assert_len_1_or_ngrps(pch, ngrps, "pch", allow_character = TRUE) + if (length(pch) == 1) pch = rep(pch, ngrps) + } + + return(pch) +} + + +by_lty = function(ngrps, type, lty = NULL) { + # We only care about line types, otherwise return NULL + if (!type %in% c("l", "b", "o", "c", "h", "s", "S", "ribbon", "barplot", "boxplot", "rect", "spineplot", "segments", "qq", "abline", "hline", "vline")) { + lty = NULL + + # special "by" convenience keyword + } else if (is_by_keyword(lty)) { + lty_dict = c("solid", "dashed", "dotted", "dotdash", "longdash", "twodash") + par_lty = par("lty") + + if (!par_lty %in% lty_dict) { + warning( + "\nBespoke lty specifications (i.e., using string combinations) are not ", + "currently supported alongside the lty='by' keyword argument. ", + "Defaulting to 1 and looping from there.\n" + ) + par_lty = 1 + } else { + par_lty = which(par_lty == lty_dict) + } + lty = 1:ngrps + par_lty - 1 + # correctly recycle if over max lty type + if (max(lty) > 6L) { + lty_below = lty[lty <= 6L] + lty_above = lty[lty > 6L] + lty_above = rep_len(1:6, length(lty_above)) + lty = c(lty_below, lty_above) + } + + # NULL -> solid (or default) line + } else if (is.null(lty)) { + if (!identical(type, "boxplot")) { + lty = rep(par("lty"), ngrps) + } + + # atomic vector: sanity check length + } else if (is.atomic(lty) && is.vector(lty)) { + assert_len_1_or_ngrps(lty, ngrps, "lty") + if (length(lty) == 1) lty = rep(lty, ngrps) + } + + lty +} + + +by_lwd = function(ngrps, type, lwd = NULL) { + lwd_base = par("lwd") + lwd_floor = lwd_base / min(5, max((ngrps - 1), 1)) + lwd_ceiling = lwd_base * min(5, ngrps) + + no_lwd = FALSE + # special "by" convenience keyword + if (is_by_keyword(lwd)) { + no_lwd = TRUE # skip checks below + lwd = seq(lwd_floor, lwd_ceiling, length.out = ngrps) + } else if (is.null(lwd)) { + no_lwd = TRUE + lwd = NULL + } + + if (!no_lwd) { + assert_len_1_or_ngrps(lwd, ngrps, "lwd") + if (length(lwd) == 1) lwd = rep(lwd, ngrps) + } + + return(lwd) +} + + +by_cex = function(ngrps, type, bubble = FALSE, cex = NULL) { + no_cex = FALSE + # special "by" convenience keyword + if (is_by_keyword(cex)) { + no_cex = TRUE # skip checks below + cex = rescale_num(c(1:ngrps), to = c(1, 2.5)) + } else if (is.null(cex)) { + no_cex = TRUE + # cex = NULL + # can't leave cex as NULL otherwise JIT cex_fct_adj adjustment in + # draw_legend() won't work later + cex = 1 + cex = rep(cex, ngrps) + } + + # placehodler + if (bubble) no_cex = TRUE + + if (!no_cex) { + assert_len_1_or_ngrps(cex, ngrps, "cex") + if (length(cex) == 1) cex = rep(cex, ngrps) + } + + return(cex) +} + + +# +## helper functions ----- +# apply_alpha = function(cols, alpha, adjustcolor) { if (is.null(cols) || is.null(alpha) || identical(alpha, 0)) { @@ -83,6 +368,17 @@ apply_alpha = function(cols, alpha, adjustcolor) { adjustcolor(cols, alpha.f = alpha) } +# Apply the lighter-but-opaque tint (PR #614) to each colour in a vector. +lighten_fill = function(cols) { + if (is.null(cols)) return(cols) + vapply( + cols, + function(cc) if (is_achromatic(cc)) "lightgray" else seq_palette(cc, n = 3)[3], + character(1), + USE.NAMES = FALSE + ) +} + is_by_keyword = function(x) { is.character(x) && length(x) == 1 && !is.na(x) && identical(x, "by") } @@ -318,250 +614,3 @@ resolve_palette_spec = function(palette, ngrps, gradient, ordered, alpha, adjust apply_alpha(cols, alpha, adjustcolor) } - -# -## subsidiary functions ----- -# - -by_col = function(col, palette, alpha, by_ordered, by_continuous, ngrps, adjustcolor) { - ordered = if (is.null(by_ordered)) FALSE else by_ordered - gradient = if (is.null(by_continuous)) FALSE else by_continuous - assert_logical(ordered) - assert_logical(gradient) - - if (is.null(alpha)) alpha = 1 - if (gradient) ngrps = 100L - - if (is_by_keyword(col)) col = NULL - - pal_theme = if (ordered || gradient) { - get_tpar("palette.sequential", default = NULL) - } else { - get_tpar("palette.qualitative", default = NULL) - } - - # Resolve a (possibly relative) col.default against the qualitative palette. - # A relative (negative) index also drops the chosen colour from the grouped - # palette, so e.g. `col.default = -1` uses the palette's leading colour for - # single-group displays and the palette-minus-that-colour for grouped ones - # (see resolve_col_default). col.default only applies to qualitative displays. - if (!ordered && !gradient) { - cd = resolve_col_default(get_tpar("col.default", default = NULL), pal_theme) - pal_theme = cd[["palette"]] - # Single-group default colour (theme-settable via tpar). When NULL, defer to - # the usual palette resolution below (first qualitative colour, or palette()[1]). - if (is.null(col) && ngrps == 1L) { - col = cd[["col_default"]] - } - } - - cols = resolve_manual_colors(col, ngrps, gradient, ordered, alpha, adjustcolor) - if (!is.null(cols)) { - return(cols) - } - - cols = resolve_palette_colors( - palette = palette, - theme_palette = pal_theme, - ngrps = ngrps, - ordered = ordered, - gradient = gradient, - alpha = alpha, - adjustcolor = adjustcolor - ) - - cols -} - - -by_bg = function(bg, fill, col, palette, alpha, by_ordered, by_continuous, ngrps, type, by, ribbon.alpha, adjustcolor) { - if (is.null(bg) && !is.null(fill)) bg = fill - if (!is.null(bg) && length(bg) == 1 && is.numeric(bg) && bg >= 0 && bg <= 1) { - alpha = bg - bg = "by" - } - if (!is.null(bg) && length(bg) == 1 && is_by_keyword(bg)) { - ordered = if (is.null(by_ordered)) FALSE else by_ordered - gradient = if (is.null(by_continuous)) FALSE else by_continuous - pal_theme = if (ordered || gradient) { - get_tpar("palette.sequential", default = NULL) - } else { - get_tpar("palette.qualitative", default = NULL) - } - # Mirror by_col(): a relative col.default trims the grouped palette, so - # grouped fills stay in step with grouped line colours. - if (!ordered && !gradient) { - pal_theme = resolve_col_default(get_tpar("col.default", default = NULL), pal_theme)[["palette"]] - } - bg = resolve_palette_colors( - palette = palette, - theme_palette = pal_theme, - ngrps = ngrps, - ordered = if (is.null(by_ordered)) FALSE else by_ordered, - gradient = if (is.null(by_continuous)) FALSE else by_continuous, - alpha = if (is.null(alpha)) 1 else alpha, - adjustcolor = adjustcolor - ) - } else if (length(bg) != ngrps) { - bg = rep(bg, ngrps) - } - if (type == "ribbon" || (type == "boxplot" && !is.null(by))) { - if (!is.null(bg)) { - bg = adjustcolor(bg, ribbon.alpha) - } else if (!is.null(col)) { - bg = adjustcolor(col, ribbon.alpha) - } - } else if (ngrps == 1L && is.null(bg) && type %in% c("boxplot", "violin", "barplot", "histogram")) { - # Single-group fill tracks the theme's *default* colour (col.default -> - # palette[1] -> black) so that themed box/violin/bar/histogram plots match - # their own multi-group counterparts. We resolve this default independently - # of `col` so that a user-supplied outline colour (e.g. `col = "white"`) - # doesn't bleed into the fill. - fill_base = by_col( - col = NULL, palette = palette, alpha = 1, by_ordered = by_ordered, - by_continuous = by_continuous, ngrps = 1L, adjustcolor = adjustcolor - ) - # For a *chromatic* default the fill is a lighter-but-opaque tint of that - # colour (via seq_palette, the same HCL ramp used for ridge fills and legend - # swatches), so it reads cleanly over grid lines unlike alpha blending. For - # an *achromatic* default (typically black, e.g. the "bw"/"classic"/"ipsum" - # themes and the plain default) seq_palette's light endpoint can't reach the - # neutral "lightgray" used by the no-theme path and base R's hist()/boxplot() - # -- so we use that literal directly, keeping all black-default single-group - # fills consistent regardless of whether a theme palette happens to be set. - achromatic = is_achromatic(fill_base) - bg = if (achromatic) "lightgray" else seq_palette(fill_base, n = 3)[3] - } - - bg -} - - -by_pch = function(ngrps, type, pch = NULL) { - no_pch = FALSE - if (identical(type, "text")) { - pch = rep(15, ngrps) - } else if (!type %in% c("p", "b", "o", "pointrange", "errorbar", "boxplot", "qq")) { - no_pch = TRUE - pch = NULL - - # special "by" convenience keyword - } else if (is_by_keyword(pch)) { - no_pch = TRUE # skip checks below - pch = 1:ngrps + par("pch") - 1 - # correctly recycle if over max pch type - if (max(pch) > 25L) { - pch_below = pch[pch <= 25L] - pch_above = pch[pch > 25L] - pch_above = rep_len(0:25, length(pch_above)) - pch = c(pch_below, pch_above) - } - - # return NULL if not a valid point type - } else if (is.null(pch)) { - pch = par("pch") - } - - if (!no_pch) { - assert_len_1_or_ngrps(pch, ngrps, "pch", allow_character = TRUE) - if (length(pch) == 1) pch = rep(pch, ngrps) - } - - return(pch) -} - - -by_lty = function(ngrps, type, lty = NULL) { - # We only care about line types, otherwise return NULL - if (!type %in% c("l", "b", "o", "c", "h", "s", "S", "ribbon", "barplot", "boxplot", "rect", "segments", "qq", "abline", "hline", "vline")) { - lty = NULL - - # special "by" convenience keyword - } else if (is_by_keyword(lty)) { - lty_dict = c("solid", "dashed", "dotted", "dotdash", "longdash", "twodash") - par_lty = par("lty") - - if (!par_lty %in% lty_dict) { - warning( - "\nBespoke lty specifications (i.e., using string combinations) are not ", - "currently supported alongside the lty='by' keyword argument. ", - "Defaulting to 1 and looping from there.\n" - ) - par_lty = 1 - } else { - par_lty = which(par_lty == lty_dict) - } - lty = 1:ngrps + par_lty - 1 - # correctly recycle if over max lty type - if (max(lty) > 6L) { - lty_below = lty[lty <= 6L] - lty_above = lty[lty > 6L] - lty_above = rep_len(1:6, length(lty_above)) - lty = c(lty_below, lty_above) - } - - # NULL -> solid (or default) line - } else if (is.null(lty)) { - if (!identical(type, "boxplot")) { - lty = rep(par("lty"), ngrps) - } - - # atomic vector: sanity check length - } else if (is.atomic(lty) && is.vector(lty)) { - assert_len_1_or_ngrps(lty, ngrps, "lty") - if (length(lty) == 1) lty = rep(lty, ngrps) - } - - lty -} - - -by_lwd = function(ngrps, type, lwd = NULL) { - lwd_base = par("lwd") - lwd_floor = lwd_base / min(5, max((ngrps - 1), 1)) - lwd_ceiling = lwd_base * min(5, ngrps) - - no_lwd = FALSE - # special "by" convenience keyword - if (is_by_keyword(lwd)) { - no_lwd = TRUE # skip checks below - lwd = seq(lwd_floor, lwd_ceiling, length.out = ngrps) - } else if (is.null(lwd)) { - no_lwd = TRUE - lwd = NULL - } - - if (!no_lwd) { - assert_len_1_or_ngrps(lwd, ngrps, "lwd") - if (length(lwd) == 1) lwd = rep(lwd, ngrps) - } - - return(lwd) -} - - -by_cex = function(ngrps, type, bubble = FALSE, cex = NULL) { - no_cex = FALSE - # special "by" convenience keyword - if (is_by_keyword(cex)) { - no_cex = TRUE # skip checks below - cex = rescale_num(c(1:ngrps), to = c(1, 2.5)) - } else if (is.null(cex)) { - no_cex = TRUE - # cex = NULL - # can't leave cex as NULL otherwise JIT cex_fct_adj adjustment in - # draw_legend() won't work later - cex = 1 - cex = rep(cex, ngrps) - } - - # placehodler - if (bubble) no_cex = TRUE - - if (!no_cex) { - assert_len_1_or_ngrps(cex, ngrps, "cex") - if (length(cex) == 1) cex = rep(cex, ngrps) - } - - return(cex) -} diff --git a/R/legend.R b/R/legend.R index a13a51db..45c0e7bc 100644 --- a/R/legend.R +++ b/R/legend.R @@ -496,6 +496,15 @@ prepare_legend = function(settings) { } } + # Grouped (`y_by`) spineplots draw their fills inside draw_spineplot() rather + # than via the shared `bg` channel, so `settings$bg` is NULL and the legend has + # nothing to mirror. Resolve the swatch fill here (where `col` is the resolved + # group palette) into `bg`, so the rest of the legend machinery treats it like + # any other area type. The fill tracks `lighten`, matching the plotted tiles. + if (identical(settings$type, "spineplot") && isTRUE(settings$type_info[["y_by"]])) { + settings$bg = if (isTRUE(settings$lighten)) lighten_fill(col) else col + } + env2env( environment(), settings, @@ -603,7 +612,11 @@ build_legend_args = function( # Special pt.bg handling for types that need color-based fills if (identical(type, "spineplot")) { - legend_args[["pt.bg"]] = legend_args[["pt.bg"]] %||% legend_args[["col"]] + # The swatch fill comes via `bg` (resolved in prepare_legend for the grouped + # `y_by` case; NULL otherwise, falling back to the group colour). + legend_args[["pt.bg"]] = legend_args[["pt.bg"]] %||% bg %||% legend_args[["col"]] + # Conversely, the border colour is always black + legend_args[["col"]] = par("fg") } else if (identical(type, "ridge") && isFALSE(gradient)) { legend_args[["pt.bg"]] = legend_args[["pt.bg"]] %||% sapply(legend_args[["col"]], function(ccol) seq_palette(ccol, n = 2)[2]) } else { diff --git a/R/tinyplot.R b/R/tinyplot.R index 80376aa4..46dbe47e 100644 --- a/R/tinyplot.R +++ b/R/tinyplot.R @@ -908,6 +908,7 @@ tinyplot.default = function( dodge = NULL, dots = dots, flip = flip, + lighten = TRUE, # area types may override via their type_data() fn group_offsets = NULL, offsets_axis = NULL, sub = sub, diff --git a/R/type_barplot.R b/R/type_barplot.R index 15ac0b5c..d75d8372 100644 --- a/R/type_barplot.R +++ b/R/type_barplot.R @@ -41,13 +41,16 @@ #' @param drop.zeros logical. Should bars with zero height be dropped? If set #' to `FALSE` (default) a zero height bar is still drawn for which the border #' lines will still be visible. +#' @param lighten logical. Should the fills use a lighter, opaque tint of the +#' series colour(s)? Default is `TRUE`, which keeps single- and multi-group +#' displays consistent and lets the fill read cleanly over grid lines. Set to +#' `FALSE` to use the fully-saturated palette colour(s) instead. #' #' @examples #' # Basic examples of frequency tables (without y variable) #' tinyplot(~ cyl, data = mtcars, type = "barplot") #' tinyplot(~ cyl | vs, data = mtcars, type = "barplot") #' tinyplot(~ cyl | vs, data = mtcars, type = "barplot", beside = TRUE) -#' tinyplot(~ cyl | vs, data = mtcars, type = "barplot", beside = TRUE, fill = 0.2) #' #' # Reorder x variable categories either by their character levels or numeric indexes #' tinyplot(~ cyl, data = mtcars, type = "barplot", xlevels = c("8", "6", "4")) @@ -58,14 +61,14 @@ #' # `tinyplot(..., width = )` argument. It's safer to pass these args #' # through the `type_barplot()` functional equivalent. #' tinyplot( -#' ~ cyl | vs, data = mtcars, fill = 0.2, +#' ~ cyl | vs, data = mtcars, #' type = type_barplot(beside = TRUE, drop.zeros = TRUE, width = 0.65) #' ) #' #' # Example for numeric y aggregated by x (default: FUN = mean) + facets #' tinyplot( #' extra ~ ID | group, facet = "by", data = sleep, -#' type = "barplot", fill = 0.6, +#' type = "barplot", #' theme = "clean2" #' ) #' @@ -73,18 +76,19 @@ #' tinyplot( #' Freq ~ Sex | Survived, data = as.data.frame(Titanic), #' facet = ~ Class, facet.args = list(nrow = 1), -#' type = "barplot", flip = TRUE, fill = 0.6, +#' type = "barplot", flip = TRUE, #' theme = "clean2" #' ) #' -#' # Centered barplot for conditional proportions of hair color (black/brown vs. -#' # red/blond) given eye color and sex +#' # Centered barplot for conditional proportions of dark (black/brown) vs. +#' # light (red/blond) hair color, conditional on eye color and sex. +#' # Aside: use `lighten = FALSE` to avoid lightening the bar fill colors. #' hec = as.data.frame(proportions(HairEyeColor, 2:3)) #' hcols = c("black", "sienna", "indianred", "goldenrod") #' tinyplot( #' Freq ~ Eye | Hair, data = hec, #' facet = ~ Sex, facet.args = list(ncol = 1), -#' type = "barplot", center = TRUE, +#' type = type_barplot(center = TRUE, lighten = FALSE), #' flip = TRUE, yaxl = "percent", #' theme = list("clean2", palette.qualitative = hcols) #' ) @@ -98,7 +102,9 @@ #' d$offset = c(0, cumsum(d$value[1:3]), 0) #' tinyplot( #' value ~ item | I(value < 0), data = d, -#' type = type_barplot(offset = d$offset), legend = FALSE +#' type = type_barplot(offset = d$offset, lighten = FALSE), +#' col = NA, # (optional: turn off border) +#' legend = FALSE #' ) #' tinyplot_add(type = type_vline(4.5), lty = 2) #' @@ -122,18 +128,18 @@ #' pal = c("#b2182b", "#ef8a62", "#67a9cf", "#2166ac", "grey") #' tinyplot( #' share ~ question | response, data = lik, -#' type = "barplot", center = TRUE, offset = "Unsure", +#' type = type_barplot(center = TRUE, offset = "Unsure", lighten = FALSE), #' flip = TRUE, xlab = NA, ylab = NA, yaxl = "percent", -#' legend = list("top!", title = NULL), +#' legend = list("top!", title = FALSE), #' theme = list("clean2", palette.qualitative = pal), #' main = "Hypothetical Likert example with category offset" #' ) #' tinyplot_add(type = "vline") #' #' @export -type_barplot = function(width = 5/6, beside = FALSE, center = FALSE, offset = NULL, FUN = NULL, xlevels = NULL, xaxlabels = NULL, drop.zeros = FALSE) { +type_barplot = function(width = 5/6, beside = FALSE, center = FALSE, offset = NULL, FUN = NULL, xlevels = NULL, xaxlabels = NULL, drop.zeros = FALSE, lighten = TRUE) { out = list( - data = data_barplot(width = width, beside = beside, center = center, offset = offset, FUN = FUN, xlevels = xlevels, xaxlabels = xaxlabels, drop.zeros = drop.zeros), + data = data_barplot(width = width, beside = beside, center = center, offset = offset, FUN = FUN, xlevels = xlevels, xaxlabels = xaxlabels, drop.zeros = drop.zeros, lighten = lighten), draw = draw_rect(), name = "barplot" ) @@ -142,7 +148,7 @@ type_barplot = function(width = 5/6, beside = FALSE, center = FALSE, offset = NU } #' @importFrom stats aggregate -data_barplot = function(width = 5/6, beside = FALSE, center = FALSE, offset = NULL, FUN = NULL, xlevels = NULL, xaxlabels = NULL, drop.zeros = FALSE) { +data_barplot = function(width = 5/6, beside = FALSE, center = FALSE, offset = NULL, FUN = NULL, xlevels = NULL, xaxlabels = NULL, drop.zeros = FALSE, lighten = TRUE) { fun = function(settings, ...) { env2env( settings, @@ -277,6 +283,9 @@ data_barplot = function(width = 5/6, beside = FALSE, center = FALSE, offset = NU range(c(stack_range, off_range), na.rm = TRUE) * 1.02 } + ## fill lightening (see by_bg) + settings[["lighten"]] = lighten + ## default color palette ngrps = length(unique(datapoints$by)) if (ngrps == 1L && null_palette) { diff --git a/R/type_boxplot.R b/R/type_boxplot.R index 51504fe6..0e501b63 100644 --- a/R/type_boxplot.R +++ b/R/type_boxplot.R @@ -7,21 +7,38 @@ #' numeric. #' #' @inheritParams graphics::boxplot +#' @param lighten logical. Should the fills use a lighter, opaque tint of the +#' series colour(s)? Default is `TRUE`, which keeps single- and multi-group +#' displays consistent and lets the fill read cleanly over grid lines. Set to +#' `FALSE` to use the fully-saturated palette colour(s) instead. #' @examples #' # "boxplot" type convenience string -#' tinyplot(count ~ spray, data = InsectSprays, type = "boxplot") +#' tinyplot(weight ~ feed, data = chickwts, type = "boxplot") #' #' # Note: Specifying the type here is redundant. Like base plot, tinyplot #' # automatically produces a boxplot if x is a factor and y is numeric -#' tinyplot(count ~ spray, data = InsectSprays) +#' tinyplot(weight ~ feed, data = chickwts) #' -#' # Grouped boxplot example -#' tinyplot(len ~ dose | supp, data = ToothGrowth, type = "boxplot") +#' # For flipped boxplots, it's usually better to use a dynamic theme to +#' # accommodate (horizontal) y-axis labels +#' tinyplot(weight ~ feed, data = chickwts, flip = TRUE, theme = "dynamic") +#' +#' # Grouped boxplot example using a different dataset +#' # (C.f., the final example in `?boxplot`) +#' tinyplot( +#' len ~ dose | supp, data = ToothGrowth, type = "boxplot", +#' main = "Guinea Pigs' Tooth Growth", +#' legend = list(title = "Supplement"), +#' xlab = "Vitamin C dose mg", ylab = "tooth length" +#' ) #' #' # Use `type_boxplot()` to pass extra arguments for customization #' tinyplot( -#' len ~ dose | supp, data = ToothGrowth, lty = 1, -#' type = type_boxplot(boxwex = 0.3, staplewex = 0, outline = FALSE) +#' len ~ dose | supp, data = ToothGrowth, +#' type = type_boxplot(boxwex = 0.3, staplewex = 0, outline = FALSE), lty = 1, +#' legend = list(title = "Supplement"), +#' main = "Guinea Pigs' Tooth Growth", +#' xlab = "Vitamin C dose mg", ylab = "tooth length" #' ) #' @export type_boxplot = function( @@ -32,7 +49,8 @@ type_boxplot = function( outline = TRUE, boxwex = 0.8, staplewex = 0.5, - outwex = 0.5) { + outwex = 0.5, + lighten = TRUE) { out = list( draw = draw_boxplot( range = range, @@ -43,7 +61,7 @@ type_boxplot = function( boxwex = boxwex, staplewex = staplewex, outwex = outwex), - data = data_boxplot(boxwex = boxwex), + data = data_boxplot(boxwex = boxwex, lighten = lighten), name = "boxplot" ) class(out) = "tinyplot_type" @@ -89,9 +107,10 @@ draw_boxplot = function(range, width, varwidth, notch, outline, boxwex, staplewe -data_boxplot = function(boxwex = 0.8) { +data_boxplot = function(boxwex = 0.8, lighten = TRUE) { fun = function(settings, ...) { env2env(settings, environment(), c("datapoints", "by", "facet", "null_facet", "null_palette", "x", "col", "bg", "null_by")) + settings[["lighten"]] = lighten # Convert x to factor if it's not already datapoints$x = as.factor(datapoints$x) diff --git a/R/type_spineplot.R b/R/type_spineplot.R index 133dea47..d6496528 100644 --- a/R/type_spineplot.R +++ b/R/type_spineplot.R @@ -8,6 +8,16 @@ #' levels of the `x` and `y` variables (if character) or the corresponding indexes #' (if numeric) for the plot. #' @inheritParams graphics::spineplot +#' @param lighten logical. For grouped spineplots where the `y` variable is +#' itself the grouping variable (i.e. `y == by`), should the fills use a +#' lighter, opaque tint of the series colour(s)? Default is `FALSE`, i.e. the +#' fills use the fully-saturated palette colour(s). (Unlike the other area +#' types such as [`type_barplot`], where lightening is the default, spineplot +#' tiles abut one another with no gap, so the darker saturated fills read +#' better against their matching border colours.) Set to `TRUE` to opt in to +#' the lighter tint. Note that `lighten` has no effect on other spineplot +#' displays (single-group or `x == by`), which always use a sequential shading +#' ramp of the base colour. #' @examples #' # "spineplot" type convenience string #' tinyplot(Species ~ Sepal.Width, data = iris, type = "spineplot") @@ -17,55 +27,64 @@ #' tinyplot(Species ~ Sepal.Width, data = iris) #' #' # Use `type_spineplot()` to pass extra arguments for customization -#' tinyplot(Species ~ Sepal.Width, data = iris, type = type_spineplot(breaks = 4)) +#' tinyplot( +#' Species ~ Sepal.Width, data = iris, +#' type = type_spineplot(breaks = 4) +#' ) #' -#' p = palette.colors(3, "Pastel 1") -#' tinyplot(Species ~ Sepal.Width, data = iris, type = type_spineplot(breaks = 4, col = p)) -#' rm(p) +#' # Passing custom colors to the y-axis categories +#' tinyplot( +#' Species ~ Sepal.Width, data = iris, +#' type = type_spineplot(breaks = 4, col = palette.colors(3, "Pastel 1")) +#' ) #' #' # More idiomatic tinyplot way of drawing the previous plot: use y == by #' tinyplot( -#' Species ~ Sepal.Width | Species, data = iris, type = type_spineplot(breaks = 4), +#' Species ~ Sepal.Width | Species, data = iris, +#' type = type_spineplot(breaks = 4), #' palette = "Pastel 1", legend = FALSE #' ) #' -#' # Grouped and faceted spineplots. The Titanic dataset is pre-tabulated, so we -#' # pass its frequency counts via the top-level `weights` argument (which -#' # supports non-standard evaluation in the formula method). +#' ## Grouped and faceted spineplots #' #' ttnc = as.data.frame(Titanic) -#' +#' +#' # Note: The Titanic (ttnc) dataset is pre-tabulated, so we pass its frequency +#' # counts via the top-level `weights` argument (accepted via non-standard +#' # evaluation in the formula method). #' tinyplot( #' Survived ~ Sex, facet = ~ Class, data = ttnc, #' # type_spineplot(weights = ttnc$Freq), ## same thing but not NSE -#' type = "spineplot", -#' weights = Freq +#' type = "spineplot", weights = Freq #' ) -#' -#' # For grouped "by" spineplots, it's better visually to facet as well +#' +#' # Reorder x and y variable categories either by their character levels or +#' # numeric indexes. (Here we combine a top-level `weights` with constructor- +#' # level arguments passed through `type_spineplot()`.) #' tinyplot( -#' Survived ~ Sex | Class, facet = "by", data = ttnc, -#' type = "spineplot", +#' Survived ~ Sex, facet = ~ Class, data = ttnc, +#' type = type_spineplot(xlevels = c("Female", "Male"), ylevels = 2:1), #' weights = Freq #' ) #' -#' # Fancier version. Note the smart inheritance of spacing etc. +#' # For (colour) grouped "by" spineplots, it's visually better to facet too #' tinyplot( -#' Survived ~ Sex | Class, facet = "by", data = ttnc, -#' type = "spineplot", -#' weights = Freq, -#' palette = "Dark 2", facet.args = list(nrow = 1), axes = "t" +#' Survived ~ Sex | Class, data = ttnc, +#' facet = "by", +#' type = "spineplot", weights = Freq #' ) #' -#' # Reorder x and y variable categories either by their character levels or -#' # numeric indexes. (Here we combine a top-level `weights` with constructor- -#' # level arguments passed through `type_spineplot()`.) +#' # Fancier version. Note the smart inheritance of spacing etc. #' tinyplot( -#' Survived ~ Sex, facet = ~ Class, data = ttnc, weights = Freq, -#' type = type_spineplot(xlevels = c("Female", "Male"), ylevels = 2:1) +#' Survived ~ Sex | Class, data = ttnc, +#' facet = "by", facet.args = list(nrow = 1), +#' type = "spineplot", weights = Freq, +#' theme = "void", axes = "t", lty = 0, legend = FALSE, +#' main = "Who survived the Titanic disaster?", +#' sub = "Frequencies by boarding class and sex" #' ) #' -#' # Note: It's possible to use "by" on its own (without faceting), but the +#' # Aside: It's possible to use "by" on its own (without faceting), but the #' # overlaid result isn't great. We will likely overhaul this behaviour in a #' # future version of tinyplot... #' tinyplot(Survived ~ Sex | Class, data = ttnc, @@ -73,11 +92,11 @@ #' ) #' #' @export -type_spineplot = function(breaks = NULL, tol.ylab = 0.05, off = NULL, xlevels = NULL, ylevels = NULL, col = NULL, xaxlabels = NULL, yaxlabels = NULL, weights = NULL) { +type_spineplot = function(breaks = NULL, tol.ylab = 0.05, off = NULL, xlevels = NULL, ylevels = NULL, col = NULL, xaxlabels = NULL, yaxlabels = NULL, weights = NULL, lighten = FALSE) { col = col out = list( - data = data_spineplot(off = off, breaks = breaks, xlevels = xlevels, ylevels = ylevels, xaxlabels = xaxlabels, yaxlabels = yaxlabels, weights = weights), - draw = draw_spineplot(tol.ylab = tol.ylab, off = off, col = col, xaxlabels = xaxlabels, yaxlabels = yaxlabels), + data = data_spineplot(off = off, breaks = breaks, xlevels = xlevels, ylevels = ylevels, xaxlabels = xaxlabels, yaxlabels = yaxlabels, weights = weights, lighten = lighten), + draw = draw_spineplot(tol.ylab = tol.ylab, off = off, col = col, xaxlabels = xaxlabels, yaxlabels = yaxlabels, lighten = lighten), name = "spineplot" ) class(out) = "tinyplot_type" @@ -85,9 +104,10 @@ type_spineplot = function(breaks = NULL, tol.ylab = 0.05, off = NULL, xlevels = } #' @importFrom grDevices nclass.Sturges -data_spineplot = function(off = NULL, breaks = NULL, xlevels = xlevels, ylevels = ylevels, xaxlabels = NULL, yaxlabels = NULL, weights = NULL) { +data_spineplot = function(off = NULL, breaks = NULL, xlevels = xlevels, ylevels = ylevels, xaxlabels = NULL, yaxlabels = NULL, weights = NULL, lighten = FALSE) { fun = function(settings, ...) { - env2env(settings, environment(), c("datapoints", "xlim", "ylim", "facet", "facet.args", "by", "xaxb", "yaxb", "null_by", "null_facet", "col", "bg", "axes", "xaxt", "yaxt")) + env2env(settings, environment(), c("datapoints", "xlim", "ylim", "facet", "facet.args", "by", "xaxb", "yaxb", "null_by", "null_facet", "col", "bg", "axes", "frame.plot", "xaxt", "yaxt", "lwd", "lty")) + settings[["lighten"]] = lighten ## process weights: a top-level `weights` column (carried on datapoints ## via NSE) takes precedence over the constructor-level `weights` arg. @@ -260,6 +280,11 @@ data_spineplot = function(off = NULL, breaks = NULL, xlevels = xlevels, ylevels axes_orig = axes xaxt_orig = xaxt yaxt_orig = yaxt + # `frame.plot` defaults to TRUE for numeric-x spinograms (the outer box + # is drawn by draw_spineplot below, after the tiles); NULL/unset counts + # as TRUE. Preserve the user's choice before overwriting it, so the box + # honours the top-level `frame.plot` rather than the tile-border `lwd`. + frameplot_orig = !isFALSE(frame.plot) axes = FALSE frame.plot = FALSE @@ -279,6 +304,7 @@ data_spineplot = function(off = NULL, breaks = NULL, xlevels = xlevels, ylevels yaxlabels = yaxlabels, breaks = breaks, axes = axes_orig, + frame.plot = frameplot_orig, xaxt = xaxt_orig, yaxt = yaxt_orig, null_by = null_by, @@ -287,9 +313,10 @@ data_spineplot = function(off = NULL, breaks = NULL, xlevels = xlevels, ylevels ) # legend customizations + # Mirror type_barplot() + settings$legend_args[["lty"]] = settings$legend_args[["lty"]] %||% 0 settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22 settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5 - settings$legend_args[["pt.lwd"]] = settings$legend_args[["pt.lwd"]] %||% 0 settings$legend_args[["y.intersp"]] = settings$legend_args[["y.intersp"]] %||% 1.25 settings$legend_args[["seg.len"]] = settings$legend_args[["seg.len"]] %||% 1.25 @@ -303,7 +330,7 @@ data_spineplot = function(off = NULL, breaks = NULL, xlevels = xlevels, ylevels return(fun) } -draw_spineplot = function(tol.ylab = 0.05, off = NULL, col = NULL, xaxlabels = NULL, yaxlabels = NULL) { +draw_spineplot = function(tol.ylab = 0.05, off = NULL, col = NULL, xaxlabels = NULL, yaxlabels = NULL, lighten = FALSE) { fun = function(ixmin, iymin, ixmax, iymax, ilty, ilwd, icol, ibg, flip, facet_window_args, @@ -325,7 +352,6 @@ draw_spineplot = function(tol.ylab = 0.05, off = NULL, col = NULL, xaxlabels = N ## graphical parameters if (is.null(col)) { - if (is.null(ibg)) ibg = icol if (isFALSE(y_by)) { # For single-group displays, use a neutral grey ramp (gray.colors) # whenever the resolved seed colour is achromatic (e.g. the black @@ -335,18 +361,23 @@ draw_spineplot = function(tol.ylab = 0.05, off = NULL, col = NULL, xaxlabels = N # displays we never switch to grayscale: each group (including one # whose palette colour is black) follows the same seq_palette ramp so # the fills stay in sync with the legend swatches. + if (is.null(ibg)) ibg = icol gs = isTRUE(null_by) && is_achromatic(ibg) ibg = seq_palette(ibg, ny, grayscale = gs) + } else { + # When the y variable is itself the grouping (`y_by`), each band is a + # group's palette colour. The fill is resolved once in prepare_legend() + # and arrives via `ibg` -- lightened to match the other area types + # (barplot/boxplot/violin) unless `lighten` is off (issue #646). Only + # fall back to lightening the group colour `icol` here if no fill was + # supplied (e.g. a standalone draw outside the legend pipeline). + if (is.null(ibg)) ibg = if (isTRUE(lighten)) lighten_fill(icol) else icol } ibg = rep_len(ibg, ny) } else { ibg = col } - - if (type_info[["xaxt"]] %in% c("l", "t", "n") && - type_info[["yaxt"]] %in% c("l", "t", "n") && - !all(c(type_info[["xaxt"]], type_info[["yaxt"]]) == "n")) ilwd = 0 - + rect( xleft = ixmin, ybottom = iymin, xright = ixmax, ytop = iymax, lty = ilty, @@ -374,7 +405,10 @@ draw_spineplot = function(tol.ylab = 0.05, off = NULL, col = NULL, xaxlabels = N if (is_facet_position(if(flip) "bottom" else "right", ifacet, facet_window_args)) spine_axis(if (flip) 1 else 4, type = type_info[["yaxt"]], categorical = FALSE) } - if(!x.categorical && (is.null(ilwd) || ilwd > 0)) box() + # Outer box for numeric-x spinograms. This is a structural frame, so it + # follows the top-level `frame.plot` (via type_info) rather than the + # tile-border `lwd` -- otherwise `lwd = 0` would wrongly drop the box too. + if (!x.categorical && isTRUE(type_info[["frame.plot"]])) box() } return(fun) diff --git a/R/type_violin.R b/R/type_violin.R index 1c7847e7..04cf2593 100644 --- a/R/type_violin.R +++ b/R/type_violin.R @@ -9,6 +9,10 @@ #' range of the data. Default is `FALSE`. #' @param width numeric (ideally in the range `[0, 1]`, although this isn't #' enforced) giving the normalized width of the individual violins. +#' @param lighten logical. Should the fills use a lighter, opaque tint of the +#' series colour(s)? Default is `TRUE`, which keeps single- and multi-group +#' displays consistent and lets the fill read cleanly over grid lines. Set to +#' `FALSE` to use the fully-saturated palette colour(s) instead. #' @inherit stats::density details #' @details See [`type_density`] for more details and considerations related to #' bandwidth selection and kernel types. @@ -16,36 +20,37 @@ #' #' @examples #' # "violin" type convenience string -#' tinyplot(count ~ spray, data = InsectSprays, type = "violin") +#' tinyplot(weight ~ feed, data = chickwts, type = "violin") #' -#' # aside: to match the defaults of `ggplot2::geom_violin()`, use `trim = TRUE` -#' # and `joint.bw = FALSE` -#' tinyplot(count ~ spray, data = InsectSprays, type = "violin", -#' trim = TRUE, joint.bw = FALSE) +#' # to match the defaults of `ggplot2::geom_violin()`, use `trim = TRUE` and +#' # `joint.bw = FALSE` +#' tinyplot( +#' weight ~ feed, data = chickwts, +#' # type = type_violin(trim = TRUE, joint.bw = FALSE) # same but see final ex. +#' type = "violin", trim = TRUE, joint.bw = FALSE +#' ) #' -#' # use flip = TRUE to reorient the axes -#' tinyplot(count ~ spray, data = InsectSprays, type = "violin", flip = TRUE) -#' -#' # for flipped plots with long group labels, it's better to use a theme for -#' # dynamic plot resizing -#' tinytheme("clean") -#' tinyplot(weight ~ feed, data = chickwts, type = "violin", flip = TRUE) +#' # For flipped violin plots, it's usually better to use a dynamic theme to +#' # accommodate (horizontal) y-axis labels +#' tinyplot( +#' weight ~ feed, data = chickwts, type = "violin", flip = TRUE, +#' theme = "dynamic" # or "clean(2)", "classic", "minimal", etc. +#' ) #' #' # you can group by the x var to add colour (here with the original orientation) #' tinyplot(weight ~ feed | feed, data = chickwts, type = "violin", legend = FALSE) #' #' # dodged grouped violin plot example (different dataset) -#' tinyplot(len ~ dose | supp, data = ToothGrowth, type = "violin", fill = 0.2) +#' tinyplot(len ~ dose | supp, data = ToothGrowth, type = "violin") #' #' # note: above we relied on `...` argument passing alongside the "violin" #' # type convenience string. But this won't work for `width`, since it will #' # clash with the top-level `tinyplot(..., width = )` arg. To ensure -#' # correct arg passing, it's safer to use the formal `type_violin()` option. -#' tinyplot(len ~ dose | supp, data = ToothGrowth, fill = 0.2, -#' type = type_violin(width = 0.8)) -#' -#' # reset theme -#' tinytheme() +#' # correct arg passing, it's safer to use the functional `type_violin()` type. +#' tinyplot( +#' len ~ dose | supp, data = ToothGrowth, +#' type = type_violin(width = 0.75) +#' ) #' #' @importFrom stats density weighted.mean #' @importFrom stats bw.SJ bw.bcv bw.nrd bw.nrd0 bw.ucv @@ -58,7 +63,8 @@ type_violin = function( n = 512, # more args from density here? trim = FALSE, - width = 0.9 + width = 0.9, + lighten = TRUE ) { kernel = match.arg(kernel, c("gaussian", "epanechnikov", "rectangular", "triangular", "biweight", "cosine", "optcosine")) if (is.logical(joint.bw)) { @@ -67,7 +73,8 @@ type_violin = function( joint.bw = match.arg(joint.bw, c("mean", "full", "none")) out = list( data = data_violin(bw = bw, adjust = adjust, kernel = kernel, n = n, - joint.bw = joint.bw, trim = trim, width = width), + joint.bw = joint.bw, trim = trim, width = width, + lighten = lighten), # draw = NULL, # name = "polygon" draw = draw_polygon(density = NULL), @@ -78,9 +85,11 @@ type_violin = function( } data_violin = function(bw = "nrd0", adjust = 1, kernel = "gaussian", n = 512, - joint.bw = "none", trim = FALSE, width = 0.9) { + joint.bw = "none", trim = FALSE, width = 0.9, + lighten = TRUE) { fun = function(settings, ...) { env2env(settings, environment(), c("datapoints", "by", "null_palette", "facet", "ylab", "col", "bg", "log", "null_by", "null_facet")) + settings[["lighten"]] = lighten # Handle ordering based on by and facet variables diff --git a/R/zzz.R b/R/zzz.R index 6b893367..ef16d3a2 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -50,6 +50,7 @@ "iby", "ifacet", "labels", + "lighten", "lty", "lwd", "nfacet_cols", diff --git a/altdoc/pkgdown.yml b/altdoc/pkgdown.yml index 5c18c76d..bec33e38 100644 --- a/altdoc/pkgdown.yml +++ b/altdoc/pkgdown.yml @@ -2,7 +2,7 @@ altdoc: 0.7.2 pandoc: '3.10' pkgdown: 2.1.3 pkgdown_sha: ~ -last_built: 2026-06-27T04:15:54+0000 +last_built: 2026-07-02T18:34:34+0000 urls: reference: https://grantmcdermott.com/tinyplot/man article: https://grantmcdermott.com/tinyplot/vignettes diff --git a/inst/tinytest/_tinysnapshot/barplot_aggregation.svg b/inst/tinytest/_tinysnapshot/barplot_aggregation.svg index 11103164..4945c9e9 100644 --- a/inst/tinytest/_tinysnapshot/barplot_aggregation.svg +++ b/inst/tinytest/_tinysnapshot/barplot_aggregation.svg @@ -26,8 +26,8 @@ - - + + group 1 2 @@ -97,48 +97,48 @@ 2 - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/barplot_custom_xtitle.svg b/inst/tinytest/_tinysnapshot/barplot_custom_xtitle.svg index 527459bb..421ca64f 100644 --- a/inst/tinytest/_tinysnapshot/barplot_custom_xtitle.svg +++ b/inst/tinytest/_tinysnapshot/barplot_custom_xtitle.svg @@ -26,8 +26,8 @@ - - + + grp 0 1 @@ -76,26 +76,26 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/barplot_custom_ytitle.svg b/inst/tinytest/_tinysnapshot/barplot_custom_ytitle.svg index 260dc9a9..61e1ff46 100644 --- a/inst/tinytest/_tinysnapshot/barplot_custom_ytitle.svg +++ b/inst/tinytest/_tinysnapshot/barplot_custom_ytitle.svg @@ -26,8 +26,8 @@ - - + + grp 0 1 @@ -76,26 +76,26 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/barplot_facet.svg b/inst/tinytest/_tinysnapshot/barplot_facet.svg index ee5ee91a..5f098036 100644 --- a/inst/tinytest/_tinysnapshot/barplot_facet.svg +++ b/inst/tinytest/_tinysnapshot/barplot_facet.svg @@ -26,8 +26,8 @@ - - + + vs 0 1 @@ -87,20 +87,20 @@ 1 - - - - - - + + + + + + - - - - - - + + + + + + diff --git a/inst/tinytest/_tinysnapshot/barplot_facet_free.svg b/inst/tinytest/_tinysnapshot/barplot_facet_free.svg index 7477260b..4d5d9365 100644 --- a/inst/tinytest/_tinysnapshot/barplot_facet_free.svg +++ b/inst/tinytest/_tinysnapshot/barplot_facet_free.svg @@ -26,8 +26,8 @@ - - + + vs 0 1 @@ -100,20 +100,20 @@ 1 - - - - - - + + + + + + - - - - - - + + + + + + diff --git a/inst/tinytest/_tinysnapshot/barplot_flip_fancy.svg b/inst/tinytest/_tinysnapshot/barplot_flip_fancy.svg index 94cadd07..333bf6ad 100644 --- a/inst/tinytest/_tinysnapshot/barplot_flip_fancy.svg +++ b/inst/tinytest/_tinysnapshot/barplot_flip_fancy.svg @@ -26,8 +26,8 @@ - - + + Survived No Yes @@ -120,28 +120,28 @@ Crew - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + diff --git a/inst/tinytest/_tinysnapshot/barplot_group.svg b/inst/tinytest/_tinysnapshot/barplot_group.svg index bcea289e..c519a1b8 100644 --- a/inst/tinytest/_tinysnapshot/barplot_group.svg +++ b/inst/tinytest/_tinysnapshot/barplot_group.svg @@ -26,8 +26,8 @@ - - + + vs 0 1 @@ -69,12 +69,12 @@ - - - - - - + + + + + + diff --git a/inst/tinytest/_tinysnapshot/barplot_group_beside.svg b/inst/tinytest/_tinysnapshot/barplot_group_beside.svg index 5d3dc336..6b7f55fc 100644 --- a/inst/tinytest/_tinysnapshot/barplot_group_beside.svg +++ b/inst/tinytest/_tinysnapshot/barplot_group_beside.svg @@ -26,8 +26,8 @@ - - + + vs 0 1 @@ -69,12 +69,12 @@ - - - - - - + + + + + + diff --git a/inst/tinytest/_tinysnapshot/barplot_group_lighten_false.svg b/inst/tinytest/_tinysnapshot/barplot_group_lighten_false.svg new file mode 100644 index 00000000..05db0c70 --- /dev/null +++ b/inst/tinytest/_tinysnapshot/barplot_group_lighten_false.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + +vs +0 +1 + + + + + + + +cyl +Count + + +4 +6 +8 +0 +2 +4 +6 +8 +10 +12 +14 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/barplot_offset_beside_group.svg b/inst/tinytest/_tinysnapshot/barplot_offset_beside_group.svg index 1a7c6108..2a4ba693 100644 --- a/inst/tinytest/_tinysnapshot/barplot_offset_beside_group.svg +++ b/inst/tinytest/_tinysnapshot/barplot_offset_beside_group.svg @@ -26,8 +26,8 @@ - - + + group 1 2 @@ -74,26 +74,26 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/barplot_offset_stacked.svg b/inst/tinytest/_tinysnapshot/barplot_offset_stacked.svg index 2883bb5d..f770424c 100644 --- a/inst/tinytest/_tinysnapshot/barplot_offset_stacked.svg +++ b/inst/tinytest/_tinysnapshot/barplot_offset_stacked.svg @@ -26,8 +26,8 @@ - - + + Survived No Yes @@ -62,10 +62,10 @@ - - - - + + + + diff --git a/inst/tinytest/_tinysnapshot/boxplot_facet_by.svg b/inst/tinytest/_tinysnapshot/boxplot_facet_by.svg index d7ab71de..bedda878 100644 --- a/inst/tinytest/_tinysnapshot/boxplot_facet_by.svg +++ b/inst/tinytest/_tinysnapshot/boxplot_facet_by.svg @@ -26,10 +26,10 @@ - - - - + + + + Diet 1 2 @@ -226,7 +226,7 @@ - + @@ -234,7 +234,7 @@ - + @@ -243,21 +243,21 @@ - + - + - + @@ -265,49 +265,49 @@ - + - + - + - + - + - + - + @@ -316,28 +316,28 @@ - + - + - + - + @@ -345,7 +345,7 @@ - + @@ -354,7 +354,7 @@ - + @@ -363,7 +363,7 @@ - + @@ -371,7 +371,7 @@ - + @@ -379,7 +379,7 @@ - + @@ -388,21 +388,21 @@ - + - + - + @@ -411,7 +411,7 @@ - + @@ -422,14 +422,14 @@ - + - + @@ -437,63 +437,63 @@ - + - + - + - + - + - + - + - + - + @@ -502,28 +502,28 @@ - + - + - + - + @@ -531,7 +531,7 @@ - + @@ -539,7 +539,7 @@ - + @@ -547,7 +547,7 @@ - + @@ -556,35 +556,35 @@ - + - + - + - + - + diff --git a/inst/tinytest/_tinysnapshot/boxplot_facet_by_x_same.svg b/inst/tinytest/_tinysnapshot/boxplot_facet_by_x_same.svg index b1254752..9ab329b4 100644 --- a/inst/tinytest/_tinysnapshot/boxplot_facet_by_x_same.svg +++ b/inst/tinytest/_tinysnapshot/boxplot_facet_by_x_same.svg @@ -26,9 +26,9 @@ - - - + + + gear 3 4 @@ -134,7 +134,7 @@ - + @@ -143,7 +143,7 @@ - + @@ -152,7 +152,7 @@ - + diff --git a/inst/tinytest/_tinysnapshot/boxplot_groups.svg b/inst/tinytest/_tinysnapshot/boxplot_groups.svg index 93510859..275194de 100644 --- a/inst/tinytest/_tinysnapshot/boxplot_groups.svg +++ b/inst/tinytest/_tinysnapshot/boxplot_groups.svg @@ -26,8 +26,8 @@ - - + + supp OJ VC @@ -72,35 +72,35 @@ - + - + - + - + - + @@ -108,7 +108,7 @@ - + diff --git a/inst/tinytest/_tinysnapshot/boxplot_groups_argpass.svg b/inst/tinytest/_tinysnapshot/boxplot_groups_argpass.svg index 4f770701..09cac22e 100644 --- a/inst/tinytest/_tinysnapshot/boxplot_groups_argpass.svg +++ b/inst/tinytest/_tinysnapshot/boxplot_groups_argpass.svg @@ -28,8 +28,8 @@ - - + + Ascorbic acid Orange juice @@ -76,35 +76,35 @@ - + - + - + - + - + @@ -112,7 +112,7 @@ - + diff --git a/inst/tinytest/_tinysnapshot/boxplot_groups_facets_with_missings.svg b/inst/tinytest/_tinysnapshot/boxplot_groups_facets_with_missings.svg index ee62bf0a..d6050182 100644 --- a/inst/tinytest/_tinysnapshot/boxplot_groups_facets_with_missings.svg +++ b/inst/tinytest/_tinysnapshot/boxplot_groups_facets_with_missings.svg @@ -26,9 +26,9 @@ - - - + + + factor(cyl) 4 6 @@ -104,35 +104,35 @@ - + - + - + - + - + @@ -141,35 +141,35 @@ - + - + - + - + - + diff --git a/inst/tinytest/_tinysnapshot/boxplot_groups_lighten_false.svg b/inst/tinytest/_tinysnapshot/boxplot_groups_lighten_false.svg new file mode 100644 index 00000000..c763ada3 --- /dev/null +++ b/inst/tinytest/_tinysnapshot/boxplot_groups_lighten_false.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + +supp +OJ +VC + + + + + + + +dose +len + + +0.5 +1 +2 +5 +10 +15 +20 +25 +30 +35 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/boxplot_groups_x_equivalent.svg b/inst/tinytest/_tinysnapshot/boxplot_groups_x_equivalent.svg index 097e131a..c46f1dac 100644 --- a/inst/tinytest/_tinysnapshot/boxplot_groups_x_equivalent.svg +++ b/inst/tinytest/_tinysnapshot/boxplot_groups_x_equivalent.svg @@ -26,8 +26,8 @@ - - + + player2 female male @@ -66,28 +66,28 @@ - + - + - + - + diff --git a/inst/tinytest/_tinysnapshot/boxplot_groups_x_equivalent_legendFALSE.svg b/inst/tinytest/_tinysnapshot/boxplot_groups_x_equivalent_legendFALSE.svg index 01eb9005..9c1ef9e6 100644 --- a/inst/tinytest/_tinysnapshot/boxplot_groups_x_equivalent_legendFALSE.svg +++ b/inst/tinytest/_tinysnapshot/boxplot_groups_x_equivalent_legendFALSE.svg @@ -52,28 +52,28 @@ - + - + - + - + diff --git a/inst/tinytest/_tinysnapshot/boxplot_groups_x_same.svg b/inst/tinytest/_tinysnapshot/boxplot_groups_x_same.svg index 71732137..b27d4b68 100644 --- a/inst/tinytest/_tinysnapshot/boxplot_groups_x_same.svg +++ b/inst/tinytest/_tinysnapshot/boxplot_groups_x_same.svg @@ -26,9 +26,9 @@ - - - + + + Species setosa versicolor @@ -76,21 +76,21 @@ - + - + - + diff --git a/inst/tinytest/_tinysnapshot/boxplot_groups_x_same_legendFALSE.svg b/inst/tinytest/_tinysnapshot/boxplot_groups_x_same_legendFALSE.svg index d5658723..8d54cfa7 100644 --- a/inst/tinytest/_tinysnapshot/boxplot_groups_x_same_legendFALSE.svg +++ b/inst/tinytest/_tinysnapshot/boxplot_groups_x_same_legendFALSE.svg @@ -60,21 +60,21 @@ - + - + - + diff --git a/inst/tinytest/_tinysnapshot/df_pairs.svg b/inst/tinytest/_tinysnapshot/df_pairs.svg index 6e689cde..bbbc3b71 100644 --- a/inst/tinytest/_tinysnapshot/df_pairs.svg +++ b/inst/tinytest/_tinysnapshot/df_pairs.svg @@ -2768,30 +2768,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + 4 @@ -2825,42 +2825,42 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 @@ -2894,42 +2894,42 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 @@ -2963,45 +2963,45 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 diff --git a/inst/tinytest/_tinysnapshot/df_pairs_by.svg b/inst/tinytest/_tinysnapshot/df_pairs_by.svg index f085e2be..194d1687 100644 --- a/inst/tinytest/_tinysnapshot/df_pairs_by.svg +++ b/inst/tinytest/_tinysnapshot/df_pairs_by.svg @@ -692,21 +692,21 @@ - + - + - + @@ -1372,7 +1372,7 @@ - + @@ -1380,14 +1380,14 @@ - + - + @@ -2058,7 +2058,7 @@ - + @@ -2066,7 +2066,7 @@ - + @@ -2074,7 +2074,7 @@ - + @@ -2739,7 +2739,7 @@ - + @@ -2748,14 +2748,14 @@ - + - + @@ -2781,14 +2781,14 @@ - - - - - - - - + + + + + + + + 4 @@ -2810,14 +2810,14 @@ - - - - - - - - + + + + + + + + 4 @@ -2834,14 +2834,14 @@ 1.0 - - - - - - - - + + + + + + + + 4 @@ -2875,18 +2875,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + 2 @@ -2908,18 +2908,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + 2 @@ -2936,18 +2936,18 @@ 1.0 - - - - - - - - - - - - + + + + + + + + + + + + 2 @@ -2981,18 +2981,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + 1 @@ -3014,18 +3014,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + 1 @@ -3042,18 +3042,18 @@ 1.0 - - - - - - - - - - - - + + + + + + + + + + + + 1 @@ -3087,19 +3087,19 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + 0 @@ -3121,19 +3121,19 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + 0 @@ -3150,19 +3150,19 @@ 1.0 - - - - - - - - - - - - - + + + + + + + + + + + + + 0 @@ -3222,15 +3222,15 @@ - - - - - - - - - + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/df_pairs_labs_frames.svg b/inst/tinytest/_tinysnapshot/df_pairs_labs_frames.svg index 49b7a7d2..0ea7870b 100644 --- a/inst/tinytest/_tinysnapshot/df_pairs_labs_frames.svg +++ b/inst/tinytest/_tinysnapshot/df_pairs_labs_frames.svg @@ -742,21 +742,21 @@ - + - + - + @@ -1474,7 +1474,7 @@ - + @@ -1482,14 +1482,14 @@ - + - + @@ -2212,7 +2212,7 @@ - + @@ -2220,7 +2220,7 @@ - + @@ -2228,7 +2228,7 @@ - + @@ -2945,7 +2945,7 @@ - + @@ -2954,14 +2954,14 @@ - + - + @@ -3006,14 +3006,14 @@ - - - - - - - - + + + + + + + + 4 @@ -3029,14 +3029,14 @@ 1.0 - - - - - - - - + + + + + + + + 4 @@ -3052,14 +3052,14 @@ 1.0 - - - - - - - - + + + + + + + + 4 @@ -3109,18 +3109,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + 2 @@ -3136,18 +3136,18 @@ 1.0 - - - - - - - - - - - - + + + + + + + + + + + + 2 @@ -3163,18 +3163,18 @@ 1.0 - - - - - - - - - - - - + + + + + + + + + + + + 2 @@ -3224,18 +3224,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + 1 @@ -3251,18 +3251,18 @@ 1.0 - - - - - - - - - - - - + + + + + + + + + + + + 1 @@ -3278,18 +3278,18 @@ 1.0 - - - - - - - - - - - - + + + + + + + + + + + + 1 @@ -3339,19 +3339,19 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + 0 @@ -3367,19 +3367,19 @@ 1.0 - - - - - - - - - - - - - + + + + + + + + + + + + + 0 @@ -3395,19 +3395,19 @@ 1.0 - - - - - - - - - - - - - + + + + + + + + + + + + + 0 @@ -3469,15 +3469,15 @@ - - - - - - - - - + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/spineplot_facet_by.svg b/inst/tinytest/_tinysnapshot/spineplot_facet_by.svg index a1c75a3c..74429f28 100644 --- a/inst/tinytest/_tinysnapshot/spineplot_facet_by.svg +++ b/inst/tinytest/_tinysnapshot/spineplot_facet_by.svg @@ -26,10 +26,10 @@ - - - - + + + + Class 1st 2nd diff --git a/inst/tinytest/_tinysnapshot/spineplot_xby.svg b/inst/tinytest/_tinysnapshot/spineplot_xby.svg index f3c80f03..004b0b4f 100644 --- a/inst/tinytest/_tinysnapshot/spineplot_xby.svg +++ b/inst/tinytest/_tinysnapshot/spineplot_xby.svg @@ -26,8 +26,8 @@ - - + + Sex Female Male diff --git a/inst/tinytest/_tinysnapshot/spineplot_yby.svg b/inst/tinytest/_tinysnapshot/spineplot_yby.svg index 3cb490b6..f58d3432 100644 --- a/inst/tinytest/_tinysnapshot/spineplot_yby.svg +++ b/inst/tinytest/_tinysnapshot/spineplot_yby.svg @@ -26,9 +26,9 @@ - - - + + + Species setosa versicolor diff --git a/inst/tinytest/_tinysnapshot/spineplot_yby_lighten_false.svg b/inst/tinytest/_tinysnapshot/spineplot_yby_lighten_false.svg new file mode 100644 index 00000000..f58d3432 --- /dev/null +++ b/inst/tinytest/_tinysnapshot/spineplot_yby_lighten_false.svg @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + +Species +setosa +versicolor +virginica + + + + + + + +Sepal.Width +Species + + + + + + + + + + + + + + + + + + + + + +2 +2.5 +3 +3.5 +4 +virginica +versicolor +setosa + + + + + + + +0.0 +0.2 +0.4 +0.6 +0.8 +1.0 + + + + + + + + + + + + + + + + + +2 +2.5 +3 +3.5 +4 +virginica +versicolor +setosa + + + + + + + +0.0 +0.2 +0.4 +0.6 +0.8 +1.0 + + + + + + + + + + + + + + + + + +2 +2.5 +3 +3.5 +4 +virginica +versicolor +setosa + + + + + + + +0.0 +0.2 +0.4 +0.6 +0.8 +1.0 + + + + diff --git a/inst/tinytest/_tinysnapshot/spineplot_yby_lighten_true.svg b/inst/tinytest/_tinysnapshot/spineplot_yby_lighten_true.svg new file mode 100644 index 00000000..4ec2f2c1 --- /dev/null +++ b/inst/tinytest/_tinysnapshot/spineplot_yby_lighten_true.svg @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + +Species +setosa +versicolor +virginica + + + + + + + +Sepal.Width +Species + + + + + + + + + + + + + + + + + + + + + +2 +2.5 +3 +3.5 +4 +virginica +versicolor +setosa + + + + + + + +0.0 +0.2 +0.4 +0.6 +0.8 +1.0 + + + + + + + + + + + + + + + + + +2 +2.5 +3 +3.5 +4 +virginica +versicolor +setosa + + + + + + + +0.0 +0.2 +0.4 +0.6 +0.8 +1.0 + + + + + + + + + + + + + + + + + +2 +2.5 +3 +3.5 +4 +virginica +versicolor +setosa + + + + + + + +0.0 +0.2 +0.4 +0.6 +0.8 +1.0 + + + + diff --git a/inst/tinytest/_tinysnapshot/tinyplot_add_jitter_on_grouped_boxplot.svg b/inst/tinytest/_tinysnapshot/tinyplot_add_jitter_on_grouped_boxplot.svg index 3f8b4aea..0fe33915 100644 --- a/inst/tinytest/_tinysnapshot/tinyplot_add_jitter_on_grouped_boxplot.svg +++ b/inst/tinytest/_tinysnapshot/tinyplot_add_jitter_on_grouped_boxplot.svg @@ -26,8 +26,8 @@ - - + + supp OJ VC @@ -72,35 +72,35 @@ - + - + - + - + - + @@ -108,73 +108,73 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/tinyplot_add_jitter_on_grouped_violin.svg b/inst/tinytest/_tinysnapshot/tinyplot_add_jitter_on_grouped_violin.svg index d464d24e..f83bef53 100644 --- a/inst/tinytest/_tinysnapshot/tinyplot_add_jitter_on_grouped_violin.svg +++ b/inst/tinytest/_tinysnapshot/tinyplot_add_jitter_on_grouped_violin.svg @@ -26,8 +26,8 @@ - - + + supp OJ VC @@ -68,72 +68,72 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/tinyplot_add_jitter_on_violin.svg b/inst/tinytest/_tinysnapshot/tinyplot_add_jitter_on_violin.svg index 7cd8fb86..4e5f97f6 100644 --- a/inst/tinytest/_tinysnapshot/tinyplot_add_jitter_on_violin.svg +++ b/inst/tinytest/_tinysnapshot/tinyplot_add_jitter_on_violin.svg @@ -57,156 +57,156 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/violin_by_x_equivalent.svg b/inst/tinytest/_tinysnapshot/violin_by_x_equivalent.svg index 445297fb..4c5243b7 100644 --- a/inst/tinytest/_tinysnapshot/violin_by_x_equivalent.svg +++ b/inst/tinytest/_tinysnapshot/violin_by_x_equivalent.svg @@ -52,10 +52,10 @@ - - - - + + + + diff --git a/inst/tinytest/_tinysnapshot/violin_facet_by.svg b/inst/tinytest/_tinysnapshot/violin_facet_by.svg index 4e1b42f0..3bc3000c 100644 --- a/inst/tinytest/_tinysnapshot/violin_facet_by.svg +++ b/inst/tinytest/_tinysnapshot/violin_facet_by.svg @@ -26,10 +26,10 @@ - - - - + + + + Diet 1 2 @@ -222,60 +222,60 @@ - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/violin_facet_x_by_same.svg b/inst/tinytest/_tinysnapshot/violin_facet_x_by_same.svg index 0c218eb4..d81c399d 100644 --- a/inst/tinytest/_tinysnapshot/violin_facet_x_by_same.svg +++ b/inst/tinytest/_tinysnapshot/violin_facet_x_by_same.svg @@ -26,9 +26,9 @@ - - - + + + gear 3 4 @@ -128,13 +128,13 @@ - + - + - + diff --git a/inst/tinytest/_tinysnapshot/violin_groups.svg b/inst/tinytest/_tinysnapshot/violin_groups.svg index 03f02910..f3125fc9 100644 --- a/inst/tinytest/_tinysnapshot/violin_groups.svg +++ b/inst/tinytest/_tinysnapshot/violin_groups.svg @@ -26,8 +26,8 @@ - - + + supp OJ VC @@ -68,12 +68,12 @@ - - - - - - + + + + + + diff --git a/inst/tinytest/_tinysnapshot/violin_groups_argpass.svg b/inst/tinytest/_tinysnapshot/violin_groups_argpass.svg index 72848341..3586836a 100644 --- a/inst/tinytest/_tinysnapshot/violin_groups_argpass.svg +++ b/inst/tinytest/_tinysnapshot/violin_groups_argpass.svg @@ -26,8 +26,8 @@ - - + + Ascorbic acid Orange juice @@ -72,12 +72,12 @@ - - - - - - + + + + + + diff --git a/inst/tinytest/_tinysnapshot/violin_groups_lighten_false.svg b/inst/tinytest/_tinysnapshot/violin_groups_lighten_false.svg new file mode 100644 index 00000000..acfc38f7 --- /dev/null +++ b/inst/tinytest/_tinysnapshot/violin_groups_lighten_false.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + +supp +OJ +VC + + + + + + + +dose +len + + +0.5 +1 +2 +0 +10 +20 +30 +40 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/_tinysnapshot/violin_x_by.svg b/inst/tinytest/_tinysnapshot/violin_x_by.svg index b06d7032..30686459 100644 --- a/inst/tinytest/_tinysnapshot/violin_x_by.svg +++ b/inst/tinytest/_tinysnapshot/violin_x_by.svg @@ -54,9 +54,9 @@ - - - + + + diff --git a/inst/tinytest/test-tinyplot_add.R b/inst/tinytest/test-tinyplot_add.R index d5a57836..e2921243 100644 --- a/inst/tinytest/test-tinyplot_add.R +++ b/inst/tinytest/test-tinyplot_add.R @@ -99,7 +99,7 @@ expect_snapshot_plot(f, label = "tinyplot_add_no_recursive_margins") f = function() { set.seed(42) tinyplot(Sepal.Length ~ Species, data = iris, type = "violin") - tinyplot_add(type = "jitter", cex = 0.5, alpha = 0.3) + tinyplot_add(type = "jitter") } expect_snapshot_plot(f, label = "tinyplot_add_jitter_on_violin") @@ -107,15 +107,15 @@ expect_snapshot_plot(f, label = "tinyplot_add_jitter_on_violin") f = function() { set.seed(42) tinyplot(len ~ dose | supp, data = ToothGrowth, type = "boxplot") - tinyplot_add(type = "jitter", cex = 0.5, alpha = 0.3) + tinyplot_add(type = "jitter") } expect_snapshot_plot(f, label = "tinyplot_add_jitter_on_grouped_boxplot") # jitter layer on top of grouped violin (#493) f = function() { set.seed(42) - tinyplot(len ~ dose | supp, data = ToothGrowth, type = "violin", bg = 0.2) - tinyplot_add(type = "jitter", cex = 0.5, alpha = 0.3) + tinyplot(len ~ dose | supp, data = ToothGrowth, type = "violin") + tinyplot_add(type = "jitter") } expect_snapshot_plot(f, label = "tinyplot_add_jitter_on_grouped_violin") diff --git a/inst/tinytest/test-type_barplot.R b/inst/tinytest/test-type_barplot.R index 275930f9..a994a37a 100644 --- a/inst/tinytest/test-type_barplot.R +++ b/inst/tinytest/test-type_barplot.R @@ -13,31 +13,30 @@ expect_snapshot_plot(f, label = "barplot_group") f = function() { - tinyplot(~ cyl | vs, data = mtcars, type = "barplot", beside = TRUE, fill = 0.2) + tinyplot(~ cyl | vs, data = mtcars, type = "barplot", beside = TRUE) } expect_snapshot_plot(f, label = "barplot_group_beside") f = function() { - tinyplot(~ cyl | vs, data = mtcars, type = "barplot", fill = 0.2, - facet = "by") + tinyplot(~ cyl | vs, data = mtcars, type = "barplot", facet = "by") } expect_snapshot_plot(f, label = "barplot_facet") f = function() { - tinyplot(~ cyl | vs, data = mtcars, type = "barplot", fill = 0.2, + tinyplot(~ cyl | vs, data = mtcars, type = "barplot", facet = "by", facet.args = list(free = TRUE)) } expect_snapshot_plot(f, label = "barplot_facet_free") f = function() { tinyplot(extra ~ ID | group, facet = "by", data = sleep, - type = "barplot", beside = TRUE, fill = 0.6) + type = "barplot", beside = TRUE) } expect_snapshot_plot(f, label = "barplot_aggregation") f = function() { tinyplot(Freq ~ Sex | Survived, facet = ~ Class, data = as.data.frame(Titanic), - type = "barplot", flip = TRUE, fill = 0.6, beside = TRUE) + type = "barplot", flip = TRUE, beside = TRUE) } expect_snapshot_plot(f, label = "barplot_flip_fancy") @@ -150,14 +149,16 @@ hec = as.data.frame(proportions(HairEyeColor, 2:3)) # Character offset auto-places a set-aside category (the #420 use case) f = function() { tinyplot(Freq ~ Eye | Hair, facet = Sex ~ 1, data = hec, type = "barplot", - center = TRUE, flip = TRUE, yaxl = "percent", offset = "Red") + center = TRUE, flip = TRUE, lighten = FALSE, offset = "Red", + yaxl = "percent") } expect_snapshot_plot(f, label = "barplot_offset_aside") # Named numeric offset places a set-aside category at an explicit baseline f = function() { tinyplot(Freq ~ Eye | Hair, facet = Sex ~ 1, data = hec, type = "barplot", - center = TRUE, flip = TRUE, yaxl = "percent", offset = c(Red = 1.1)) + center = TRUE, flip = TRUE, lighten = FALSE, offset = c(Red = 1.1), + yaxl = "percent") } expect_snapshot_plot(f, label = "barplot_offset_aside_explicit") @@ -182,3 +183,12 @@ f = function() { tinyplot(~ Species, data = iris, type = "barplot", xlab = NA) } expect_snapshot_plot(f, label = "barplot_xlab_na_issue635") + +# Lighter opaque grouped fills (#646). The grouped bar fill should default to a +# lighter, opaque tint of the palette colour, and `lighten = FALSE` should fall +# back to the fully-saturated palette colour. +f = function() { + tinyplot(~ cyl | vs, data = mtcars, + type = type_barplot(lighten = FALSE), theme = "clean2") +} +expect_snapshot_plot(f, label = "barplot_group_lighten_false") diff --git a/inst/tinytest/test-type_boxplot.R b/inst/tinytest/test-type_boxplot.R index 6ae7e924..2a7ae573 100644 --- a/inst/tinytest/test-type_boxplot.R +++ b/inst/tinytest/test-type_boxplot.R @@ -107,3 +107,13 @@ f = function() { suppressWarnings(plt(mpg ~ factor(am), data = mtcars)) } expect_snapshot_plot(f, label = "boxplot_auto_factor") + +# +## lighter opaque grouped fills (#646); `lighten = FALSE` keeps the legacy +## semi-transparent fill + +f = function() { + plt(len ~ dose | supp, data = ToothGrowth, + type = type_boxplot(lighten = FALSE), theme = "clean2") +} +expect_snapshot_plot(f, label = "boxplot_groups_lighten_false") diff --git a/inst/tinytest/test-type_spineplot.R b/inst/tinytest/test-type_spineplot.R index 38ba8081..f47466a6 100644 --- a/inst/tinytest/test-type_spineplot.R +++ b/inst/tinytest/test-type_spineplot.R @@ -79,7 +79,7 @@ f = function() { tinyplot( Survived ~ Sex | Class, facet = "by", data = ttnc, type = type_spineplot(weights = ttnc$Freq), - palette = "Dark 2", axes = "t", legend = FALSE + palette = "Dark 2", axes = "t", legend = FALSE, lwd = 0 ) } expect_snapshot_plot(f, label = "spineplot_facet_by_fancy") @@ -112,3 +112,18 @@ f = function() { theme = "dynamic", type = "spineplot", xlab = NA) } expect_snapshot_plot(f, label = "spineplot_xlab_na_issue635") + +# +## spineplots default to saturated fills (lighten = FALSE); `lighten = TRUE` +## opts in to the lighter-but-opaque tint used by the other area types (#646). +## (The default saturated look is already covered by the `spineplot_yby` case +## above.) + +f = function() { + tinyplot( + Species ~ Sepal.Width | Species, data = iris, + type = type_spineplot(breaks = 4, lighten = TRUE), + palette = "Pastel 1" + ) +} +expect_snapshot_plot(f, label = "spineplot_yby_lighten_true") diff --git a/inst/tinytest/test-type_violin.R b/inst/tinytest/test-type_violin.R index e4dc2b91..3bfb6676 100644 --- a/inst/tinytest/test-type_violin.R +++ b/inst/tinytest/test-type_violin.R @@ -35,7 +35,6 @@ expect_snapshot_plot(f, label = "violin_groups") f = function() { plt(len ~ dose | factor(supp, labels = c("Ascorbic acid", "Orange juice")), data = ToothGrowth, - fill = 0.2, main = "Guinea Pigs' Tooth Growth", xlab = "Vitamin C dose mg", ylab = "tooth length", type = type_violin(trim = TRUE, joint.bw = FALSE), @@ -67,7 +66,7 @@ expect_snapshot_plot(f, label = "violin_by_x_equivalent") ## facets f = function() { - plt(weight ~ Time | Diet, ChickWeight, type = "violin", facet = "by", fill = 0.2) + plt(weight ~ Time | Diet, ChickWeight, type = "violin", facet = "by") } expect_snapshot_plot(f, label = "violin_facet_by") @@ -80,3 +79,13 @@ expect_snapshot_plot(f, label = "violin_facet_x_by_same") # plt(mpg ~ gear | factor(cyl), data = mtcars, type = "violin", facet = ~am) # } # expect_snapshot_plot(f, label = "violin_groups_facets_with_missings") + +# +## lighter opaque grouped fills (#646); `lighten = FALSE` uses the saturated +## palette colour(s) + +f = function() { + plt(len ~ dose | supp, data = ToothGrowth, + type = type_violin(lighten = FALSE), theme = "clean2") +} +expect_snapshot_plot(f, label = "violin_groups_lighten_false") diff --git a/man/type_barplot.Rd b/man/type_barplot.Rd index c6944d6d..9867fbe6 100644 --- a/man/type_barplot.Rd +++ b/man/type_barplot.Rd @@ -12,7 +12,8 @@ type_barplot( FUN = NULL, xlevels = NULL, xaxlabels = NULL, - drop.zeros = FALSE + drop.zeros = FALSE, + lighten = TRUE ) } \arguments{ @@ -59,6 +60,11 @@ defaulting to the levels of \code{x}.} \item{drop.zeros}{logical. Should bars with zero height be dropped? If set to \code{FALSE} (default) a zero height bar is still drawn for which the border lines will still be visible.} + +\item{lighten}{logical. Should the fills use a lighter, opaque tint of the +series colour(s)? Default is \code{TRUE}, which keeps single- and multi-group +displays consistent and lets the fill read cleanly over grid lines. Set to +\code{FALSE} to use the fully-saturated palette colour(s) instead.} } \description{ Type function for producing barplots. For formulas of type @@ -72,7 +78,6 @@ using some function (default: mean). tinyplot(~ cyl, data = mtcars, type = "barplot") tinyplot(~ cyl | vs, data = mtcars, type = "barplot") tinyplot(~ cyl | vs, data = mtcars, type = "barplot", beside = TRUE) -tinyplot(~ cyl | vs, data = mtcars, type = "barplot", beside = TRUE, fill = 0.2) # Reorder x variable categories either by their character levels or numeric indexes tinyplot(~ cyl, data = mtcars, type = "barplot", xlevels = c("8", "6", "4")) @@ -83,14 +88,14 @@ tinyplot(~ cyl, data = mtcars, type = "barplot", xlevels = 3:1) # `tinyplot(..., width = )` argument. It's safer to pass these args # through the `type_barplot()` functional equivalent. tinyplot( - ~ cyl | vs, data = mtcars, fill = 0.2, + ~ cyl | vs, data = mtcars, type = type_barplot(beside = TRUE, drop.zeros = TRUE, width = 0.65) ) # Example for numeric y aggregated by x (default: FUN = mean) + facets tinyplot( extra ~ ID | group, facet = "by", data = sleep, - type = "barplot", fill = 0.6, + type = "barplot", theme = "clean2" ) @@ -98,18 +103,19 @@ tinyplot( tinyplot( Freq ~ Sex | Survived, data = as.data.frame(Titanic), facet = ~ Class, facet.args = list(nrow = 1), - type = "barplot", flip = TRUE, fill = 0.6, + type = "barplot", flip = TRUE, theme = "clean2" ) -# Centered barplot for conditional proportions of hair color (black/brown vs. -# red/blond) given eye color and sex +# Centered barplot for conditional proportions of dark (black/brown) vs. +# light (red/blond) hair color, conditional on eye color and sex. +# Aside: use `lighten = FALSE` to avoid lightening the bar fill colors. hec = as.data.frame(proportions(HairEyeColor, 2:3)) hcols = c("black", "sienna", "indianred", "goldenrod") tinyplot( Freq ~ Eye | Hair, data = hec, facet = ~ Sex, facet.args = list(ncol = 1), - type = "barplot", center = TRUE, + type = type_barplot(center = TRUE, lighten = FALSE), flip = TRUE, yaxl = "percent", theme = list("clean2", palette.qualitative = hcols) ) @@ -123,7 +129,9 @@ d$item = factor(d$item, levels = d$item) d$offset = c(0, cumsum(d$value[1:3]), 0) tinyplot( value ~ item | I(value < 0), data = d, - type = type_barplot(offset = d$offset), legend = FALSE + type = type_barplot(offset = d$offset, lighten = FALSE), + col = NA, # (optional: turn off border) + legend = FALSE ) tinyplot_add(type = type_vline(4.5), lty = 2) @@ -147,9 +155,9 @@ lik$share = c( # proportions summing to 1 within each question pal = c("#b2182b", "#ef8a62", "#67a9cf", "#2166ac", "grey") tinyplot( share ~ question | response, data = lik, - type = "barplot", center = TRUE, offset = "Unsure", + type = type_barplot(center = TRUE, offset = "Unsure", lighten = FALSE), flip = TRUE, xlab = NA, ylab = NA, yaxl = "percent", - legend = list("top!", title = NULL), + legend = list("top!", title = FALSE), theme = list("clean2", palette.qualitative = pal), main = "Hypothetical Likert example with category offset" ) diff --git a/man/type_boxplot.Rd b/man/type_boxplot.Rd index a23a6855..0aeee668 100644 --- a/man/type_boxplot.Rd +++ b/man/type_boxplot.Rd @@ -12,7 +12,8 @@ type_boxplot( outline = TRUE, boxwex = 0.8, staplewex = 0.5, - outwex = 0.5 + outwex = 0.5, + lighten = TRUE ) } \arguments{ @@ -47,6 +48,11 @@ type_boxplot( \item{outwex}{outlier line width expansion, proportional to box width.} + +\item{lighten}{logical. Should the fills use a lighter, opaque tint of the +series colour(s)? Default is \code{TRUE}, which keeps single- and multi-group +displays consistent and lets the fill read cleanly over grid lines. Set to +\code{FALSE} to use the fully-saturated palette colour(s) instead.} } \description{ Type function for producing box-and-whisker plots. @@ -57,18 +63,31 @@ numeric. } \examples{ # "boxplot" type convenience string -tinyplot(count ~ spray, data = InsectSprays, type = "boxplot") +tinyplot(weight ~ feed, data = chickwts, type = "boxplot") # Note: Specifying the type here is redundant. Like base plot, tinyplot # automatically produces a boxplot if x is a factor and y is numeric -tinyplot(count ~ spray, data = InsectSprays) +tinyplot(weight ~ feed, data = chickwts) + +# For flipped boxplots, it's usually better to use a dynamic theme to +# accommodate (horizontal) y-axis labels +tinyplot(weight ~ feed, data = chickwts, flip = TRUE, theme = "dynamic") -# Grouped boxplot example -tinyplot(len ~ dose | supp, data = ToothGrowth, type = "boxplot") +# Grouped boxplot example using a different dataset +# (C.f., the final example in `?boxplot`) +tinyplot( + len ~ dose | supp, data = ToothGrowth, type = "boxplot", + main = "Guinea Pigs' Tooth Growth", + legend = list(title = "Supplement"), + xlab = "Vitamin C dose mg", ylab = "tooth length" +) # Use `type_boxplot()` to pass extra arguments for customization tinyplot( - len ~ dose | supp, data = ToothGrowth, lty = 1, - type = type_boxplot(boxwex = 0.3, staplewex = 0, outline = FALSE) + len ~ dose | supp, data = ToothGrowth, + type = type_boxplot(boxwex = 0.3, staplewex = 0, outline = FALSE), lty = 1, + legend = list(title = "Supplement"), + main = "Guinea Pigs' Tooth Growth", + xlab = "Vitamin C dose mg", ylab = "tooth length" ) } diff --git a/man/type_spineplot.Rd b/man/type_spineplot.Rd index f318c90f..041b56d6 100644 --- a/man/type_spineplot.Rd +++ b/man/type_spineplot.Rd @@ -13,7 +13,8 @@ type_spineplot( col = NULL, xaxlabels = NULL, yaxlabels = NULL, - weights = NULL + weights = NULL, + lighten = FALSE ) } \arguments{ @@ -44,6 +45,17 @@ levels of the \code{x} and \code{y} variables (if character) or the correspondin observation in the data. If \code{NULL} all weights are implicitly assumed to be 1. If \code{x} is already a 2-way table, the weights are ignored.} + +\item{lighten}{logical. For grouped spineplots where the \code{y} variable is +itself the grouping variable (i.e. \code{y == by}), should the fills use a +lighter, opaque tint of the series colour(s)? Default is \code{FALSE}, i.e. the +fills use the fully-saturated palette colour(s). (Unlike the other area +types such as \code{\link{type_barplot}}, where lightening is the default, spineplot +tiles abut one another with no gap, so the darker saturated fills read +better against their matching border colours.) Set to \code{TRUE} to opt in to +the lighter tint. Note that \code{lighten} has no effect on other spineplot +displays (single-group or \code{x == by}), which always use a sequential shading +ramp of the base colour.} } \description{ Type function(s) for producing spineplots and spinograms, which @@ -60,55 +72,64 @@ tinyplot(Species ~ Sepal.Width, data = iris, type = "spineplot") tinyplot(Species ~ Sepal.Width, data = iris) # Use `type_spineplot()` to pass extra arguments for customization -tinyplot(Species ~ Sepal.Width, data = iris, type = type_spineplot(breaks = 4)) +tinyplot( + Species ~ Sepal.Width, data = iris, + type = type_spineplot(breaks = 4) +) -p = palette.colors(3, "Pastel 1") -tinyplot(Species ~ Sepal.Width, data = iris, type = type_spineplot(breaks = 4, col = p)) -rm(p) +# Passing custom colors to the y-axis categories +tinyplot( + Species ~ Sepal.Width, data = iris, + type = type_spineplot(breaks = 4, col = palette.colors(3, "Pastel 1")) +) # More idiomatic tinyplot way of drawing the previous plot: use y == by tinyplot( - Species ~ Sepal.Width | Species, data = iris, type = type_spineplot(breaks = 4), + Species ~ Sepal.Width | Species, data = iris, + type = type_spineplot(breaks = 4), palette = "Pastel 1", legend = FALSE ) -# Grouped and faceted spineplots. The Titanic dataset is pre-tabulated, so we -# pass its frequency counts via the top-level `weights` argument (which -# supports non-standard evaluation in the formula method). +## Grouped and faceted spineplots ttnc = as.data.frame(Titanic) +# Note: The Titanic (ttnc) dataset is pre-tabulated, so we pass its frequency +# counts via the top-level `weights` argument (accepted via non-standard +# evaluation in the formula method). tinyplot( Survived ~ Sex, facet = ~ Class, data = ttnc, # type_spineplot(weights = ttnc$Freq), ## same thing but not NSE - type = "spineplot", - weights = Freq + type = "spineplot", weights = Freq ) -# For grouped "by" spineplots, it's better visually to facet as well +# Reorder x and y variable categories either by their character levels or +# numeric indexes. (Here we combine a top-level `weights` with constructor- +# level arguments passed through `type_spineplot()`.) tinyplot( - Survived ~ Sex | Class, facet = "by", data = ttnc, - type = "spineplot", + Survived ~ Sex, facet = ~ Class, data = ttnc, + type = type_spineplot(xlevels = c("Female", "Male"), ylevels = 2:1), weights = Freq ) -# Fancier version. Note the smart inheritance of spacing etc. +# For (colour) grouped "by" spineplots, it's visually better to facet too tinyplot( - Survived ~ Sex | Class, facet = "by", data = ttnc, - type = "spineplot", - weights = Freq, - palette = "Dark 2", facet.args = list(nrow = 1), axes = "t" + Survived ~ Sex | Class, data = ttnc, + facet = "by", + type = "spineplot", weights = Freq ) -# Reorder x and y variable categories either by their character levels or -# numeric indexes. (Here we combine a top-level `weights` with constructor- -# level arguments passed through `type_spineplot()`.) +# Fancier version. Note the smart inheritance of spacing etc. tinyplot( - Survived ~ Sex, facet = ~ Class, data = ttnc, weights = Freq, - type = type_spineplot(xlevels = c("Female", "Male"), ylevels = 2:1) + Survived ~ Sex | Class, data = ttnc, + facet = "by", facet.args = list(nrow = 1), + type = "spineplot", weights = Freq, + theme = "void", axes = "t", lty = 0, legend = FALSE, + main = "Who survived the Titanic disaster?", + sub = "Frequencies by boarding class and sex" ) -# Note: It's possible to use "by" on its own (without faceting), but the +# Aside: It's possible to use "by" on its own (without faceting), but the # overlaid result isn't great. We will likely overhaul this behaviour in a # future version of tinyplot... tinyplot(Survived ~ Sex | Class, data = ttnc, diff --git a/man/type_violin.Rd b/man/type_violin.Rd index 3251f5e8..b63c578b 100644 --- a/man/type_violin.Rd +++ b/man/type_violin.Rd @@ -12,7 +12,8 @@ type_violin( "cosine", "optcosine"), n = 512, trim = FALSE, - width = 0.9 + width = 0.9, + lighten = TRUE ) } \arguments{ @@ -51,6 +52,11 @@ range of the data. Default is \code{FALSE}.} \item{width}{numeric (ideally in the range \verb{[0, 1]}, although this isn't enforced) giving the normalized width of the individual violins.} + +\item{lighten}{logical. Should the fills use a lighter, opaque tint of the +series colour(s)? Default is \code{TRUE}, which keeps single- and multi-group +displays consistent and lets the fill read cleanly over grid lines. Set to +\code{FALSE} to use the fully-saturated palette colour(s) instead.} } \description{ Type function for violin plots, which are an alternative to box @@ -63,35 +69,36 @@ bandwidth selection and kernel types. } \examples{ # "violin" type convenience string -tinyplot(count ~ spray, data = InsectSprays, type = "violin") - -# aside: to match the defaults of `ggplot2::geom_violin()`, use `trim = TRUE` -# and `joint.bw = FALSE` -tinyplot(count ~ spray, data = InsectSprays, type = "violin", - trim = TRUE, joint.bw = FALSE) +tinyplot(weight ~ feed, data = chickwts, type = "violin") -# use flip = TRUE to reorient the axes -tinyplot(count ~ spray, data = InsectSprays, type = "violin", flip = TRUE) +# to match the defaults of `ggplot2::geom_violin()`, use `trim = TRUE` and +# `joint.bw = FALSE` +tinyplot( + weight ~ feed, data = chickwts, + # type = type_violin(trim = TRUE, joint.bw = FALSE) # same but see final ex. + type = "violin", trim = TRUE, joint.bw = FALSE +) -# for flipped plots with long group labels, it's better to use a theme for -# dynamic plot resizing -tinytheme("clean") -tinyplot(weight ~ feed, data = chickwts, type = "violin", flip = TRUE) +# For flipped violin plots, it's usually better to use a dynamic theme to +# accommodate (horizontal) y-axis labels +tinyplot( + weight ~ feed, data = chickwts, type = "violin", flip = TRUE, + theme = "dynamic" # or "clean(2)", "classic", "minimal", etc. +) # you can group by the x var to add colour (here with the original orientation) tinyplot(weight ~ feed | feed, data = chickwts, type = "violin", legend = FALSE) # dodged grouped violin plot example (different dataset) -tinyplot(len ~ dose | supp, data = ToothGrowth, type = "violin", fill = 0.2) +tinyplot(len ~ dose | supp, data = ToothGrowth, type = "violin") # note: above we relied on `...` argument passing alongside the "violin" # type convenience string. But this won't work for `width`, since it will # clash with the top-level `tinyplot(..., width = )` arg. To ensure -# correct arg passing, it's safer to use the formal `type_violin()` option. -tinyplot(len ~ dose | supp, data = ToothGrowth, fill = 0.2, - type = type_violin(width = 0.8)) - -# reset theme -tinytheme() +# correct arg passing, it's safer to use the functional `type_violin()` type. +tinyplot( + len ~ dose | supp, data = ToothGrowth, + type = type_violin(width = 0.75) +) } diff --git a/vignettes/gallery_figs/barplot-meat.R b/vignettes/gallery_figs/barplot-meat.R index bbdc1762..75209082 100644 --- a/vignettes/gallery_figs/barplot-meat.R +++ b/vignettes/gallery_figs/barplot-meat.R @@ -12,7 +12,7 @@ tinyplot( consumption ~ decade | type, facet = country ~ 1, data = meat, - type = "barplot", + type = "barplot", lighten = FALSE, grid = "X", flip = TRUE, legend = list("bottom!", title = NULL), diff --git a/vignettes/gallery_figs/likert.R b/vignettes/gallery_figs/likert.R index f371b884..1348b20b 100644 --- a/vignettes/gallery_figs/likert.R +++ b/vignettes/gallery_figs/likert.R @@ -19,7 +19,7 @@ pal = c("#b2182b", "#ef8a62", "#67a9cf", "#2166ac", "grey") plt( share ~ question | response, data = lik, - type = "barplot", center = TRUE, offset = "Unsure", + type = type_barplot(lighten = FALSE, center = TRUE, offset = "Unsure"), flip = TRUE, xlab = NA, ylab = NA, yaxl = "percent", legend = list("top!", title = FALSE), theme = list("clean2", palette.qualitative = pal), diff --git a/vignettes/gallery_figs/spineplot-titanic.R b/vignettes/gallery_figs/spineplot-titanic.R index 26d56e52..ef178e12 100644 --- a/vignettes/gallery_figs/spineplot-titanic.R +++ b/vignettes/gallery_figs/spineplot-titanic.R @@ -3,7 +3,7 @@ ttnc = as.data.frame(Titanic) tinyplot( Survived ~ Sex | Class, facet = "by", facet.args = list(nrow = 1), - legend = FALSE, + legend = FALSE, lty = 0, data = ttnc, type = "spineplot", weights = Freq, theme = "void", axes = "t",