Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tinytheme("ridge(2)") #302

Merged
merged 13 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Package: tinyplot
Type: Package
Title: Lightweight Extension of the Base R Graphics System
Version: 0.2.1.99
Date: 2025-01-26
Date: 2025-02-04
Authors@R:
c(
person(
Expand Down
24 changes: 23 additions & 1 deletion R/tinytheme.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#' - `"minimal"` (*): removes the plot frame (box) from `"bw"`, as well as the background for facet titles. Inspired by the **ggplot2** theme of the same name.
#' - `"ipsum"` (*): similar to `"minimal"`, except subtitle is italicised and axes titles are aligned to the far edges. Inspired by the **hrbrthemes** theme of the same name for **ggplot2**.
#' - `"dark"` (*): similar to `"minimal"`, but set against a dark background with foreground and a palette colours lightened for appropriate contrast.
#' - `"ridge"` (*): a specialized theme for ridge plots (see [`type_ridge()`]). Builds off of `"clean"`, but adds ridge-specific tweaks (e.g. default "Zissou 1" palette for discrete colors, solid horizontal grid lines, and minor adjustments to y-axis labels). Not recommended for non-ridge plots.
#' - `"ridge2"` (*): removes the plot frame (box) from `"ridge"`, but retains the x-axis line. Again, not recommended for non-ridge plots.
#' - `"tufte"`: floating axes and minimalist plot artifacts in the style of Edward Tufte.
#' - `"void"`: switches off all axes, titles, legends, etc.
#' @param ... Named arguments to override specific theme settings. These
Expand Down Expand Up @@ -72,6 +74,10 @@
#' # Customize the theme by overriding default settings
#' tinytheme("bw", fg = "green", font.main = 2, font.sub = 3, family = "Palatino")
#' p()
#'
#' # Aside: One or two specialized themes are only meant for certain plot types
#' tinytheme("ridge2")
#' tinyplot(I(cut(lat, 10)) ~ depth, data = quakes, type = "ridge")
#'
#' # Reset the theme
#' tinytheme()
Expand Down Expand Up @@ -101,6 +107,7 @@ tinytheme = function(
"default", "basic",
"clean", "clean2", "bw", "classic",
"minimal", "ipsum", "dark",
"ridge", "ridge2",
"tufte", "void"
),
...
Expand All @@ -116,7 +123,8 @@ tinytheme = function(
theme,
c(
"default",
sort(c("basic", "bw", "classic", "clean", "clean2", "dark", "ipsum", "minimal", "tufte", "void"))
sort(c("basic", "bw", "classic", "clean", "clean2", "dark", "ipsum",
"minimal", "ridge", "ridge2", "tufte", "void"))
)
)

Expand All @@ -130,6 +138,8 @@ tinytheme = function(
"dark" = theme_dark,
"ipsum" = theme_ipsum,
"minimal" = theme_minimal,
"ridge" = theme_ridge,
"ridge2" = theme_ridge2,
"tufte" = theme_tufte,
"void" = theme_void,
)
Expand Down Expand Up @@ -345,3 +355,15 @@ theme_dark = modifyList(theme_minimal, list(
palette.sequential = "Sunset"
))

# derivative of clean/clean2

theme_ridge = modifyList(theme_clean, list(
tinytheme = "ridge",
palette.qualitative = "Zissou 1",
grid = FALSE
))
theme_ridge2 = modifyList(theme_clean2, list(
tinytheme = "ridge2",
palette.qualitative = "Zissou 1",
grid = FALSE
))
150 changes: 93 additions & 57 deletions R/type_ridge.R
Original file line number Diff line number Diff line change
Expand Up @@ -100,74 +100,98 @@
#' determine which approach is optimal for their device.
#'
#' @examples
#' ## default ridge plot (using the "ridge" convenience string)
#' tinyplot(Species ~ Sepal.Width, data = iris, type = "ridge")
#' aq = transform(
#' airquality,
#' Month = factor(month.abb[Month], levels = month.abb[5:9]),
#' Month2 = factor(month.name[Month], levels = month.name[5:9]),
#' Late = ifelse(Day > 15, "Late", "Early")
#' )
#'
#' ## pass customization arguments through type_ridge(), for example...
#' # default ridge plot (using the "ridge" convenience string)
#' tinyplot(Month ~ Temp, data = aq, type = "ridge")
#'
#' ## use the same bandwidth for all densities
#' tinyplot(Species ~ Sepal.Width, data = iris,
#' type = type_ridge(bw = bw.nrd0(iris$Sepal.Width)))
#'
#' ## customized ridge plot without overlap of densities
#' tinyplot(Species ~ Sepal.Width, data = iris,
#' type = type_ridge(scale = 1),
#' bg = "light blue", col = "black")
#' # for ridge plots, we recommend pairing with the dedicated theme(s), which
#' # facilitate nicer y-axis labels, grid lines, etc.
#'
#' tinytheme("ridge")
#' tinyplot(Month ~ Temp, data = aq, type = "ridge")
#'
#' tinytheme("ridge2") # removes the plot frame (but keeps x-axis line)
#' tinyplot(Month ~ Temp, data = aq, type = "ridge")
#'
#' # the "ridge(2)" themes are especially helpful for long y labels, due to
#' # dyanmic plot adjustment
#' tinyplot(Month2 ~ Temp, data = aq, type = "ridge")
#'
#' # pass customization arguments through type_ridge()... for example, use
#' # the scale argument to change/avoid overlap of densities (more on scaling
#' # further below)
#'
#' tinyplot(Month ~ Temp, data = aq, type = type_ridge(scale = 1))
#'
#' ## by grouping is also supported. two special cases of interest:
#'
#' # 1) by == y (color by y groups)
#' tinyplot(Species ~ Sepal.Width | Species, data = iris, type = "ridge")
#' tinyplot(Month ~ Temp | Month, data = aq, type = "ridge")
#'
#' # 2) by == x (gradient coloring along x)
#' tinyplot(Species ~ Sepal.Width | Sepal.Width, data = iris, type = "ridge")
#' tinyplot(Month ~ Temp | Temp, data = aq, type = "ridge")
#'
#' # aside: pass explicit `type_ridge(col = <col>)` arg to set a common border
#' # color
#' tinyplot(Species ~ Sepal.Width | Sepal.Width, data = iris,
#' type = type_ridge(col = "white"))
#' # aside: pass explicit `type_ridge(col = <col>)` arg to set a different
#' # border color
#' tinyplot(Month ~ Temp | Temp, data = aq, type = type_ridge(col = "white"))
#'
#' ## gradient coloring along the x-axis can also be invoked manually without
#' ## a legend (the following tinyplot calls are all equivalent)
#' tinyplot(Species ~ Sepal.Width, data = iris, type = type_ridge(gradient = TRUE))
#' # tinyplot(Species ~ Sepal.Width, data = iris, type = type_ridge(gradient = "viridis"))
#' # tinyplot(Species ~ Sepal.Width, data = iris, type = type_ridge(gradient = hcl.colors(512)))
#' # gradient coloring along the x-axis can also be invoked manually without
#' # a legend (the next two tinyplot calls are equivalent)
#'
#' # tinyplot(Month ~ Temp, data = aq, type = type_ridge(gradient = "agsunset"))
#' tinyplot(Month ~ Temp, data = aq, type = type_ridge(gradient = TRUE))
#'
#' # aside: when combining gradient fill with alpha transparency, it may be
#' # better to use the raster-based approach (test on your graphics device)
#'
#' ## aside: when combining gradient fill with alpha transparency, it may be
#' ## better to use the raster-based approach (test on your graphics device)
#' tinyplot(Species ~ Sepal.Width, data = iris,
#' tinyplot(Month ~ Temp, data = aq,
#' type = type_ridge(gradient = TRUE, alpha = 0.5),
#' main = "polygon fill (default)")
#' tinyplot(Species ~ Sepal.Width, data = iris,
#' tinyplot(Month ~ Temp, data = aq,
#' type = type_ridge(gradient = TRUE, alpha = 0.5, raster = TRUE),
#' main = "raster fill")
#'
#' ## highlighting only the center 50% of the density (between 25% and 75% quantile)
#' tinyplot(Species ~ Sepal.Width, data = iris, col = "white",
#' type = type_ridge(
#' gradient = hcl.colors(3, "Dark Mint")[c(2, 1, 2)],
#' probs = c(0.25, 0.75)))
#' # highlighting only the center 50% of the density (i.e., 25%-75% quantiles)
#' tinyplot(Month ~ Temp, data = aq, type = type_ridge(
#' gradient = hcl.colors(3, "Dark Mint")[c(2, 1, 2)],
#' probs = c(0.25, 0.75), col = "white"))
#'
#' ## highlighting the probability distribution by color gradient (median = darkest point)
#' tinyplot(Species ~ Sepal.Width, data = iris,
#' type = type_ridge(gradient = hcl.colors(250, "Dark Mint")[c(250:1, 1:250)],
#' # highlighting the probability distribution by color gradient
#' # (darkest point = median)
#' tinyplot(Month ~ Temp, data = aq, type = type_ridge(
#' gradient = hcl.colors(250, "Dark Mint")[c(250:1, 1:250)],
#' probs = 0:500/500))
#'
#' ## with faceting and color gradient
#' aq = transform(airquality, Late = ifelse(Day > 15, "Late", "Early"))
#' # faceting also works, although we recommend switching (back) to the "ridge"
#' # theme for faceted ridge plots
#'
#' tinytheme("ridge")
#' tinyplot(Month ~ Ozone, facet = ~ Late, data = aq,
#' type = type_ridge(gradient = TRUE),
#' grid = TRUE, axes = "t", col = "white")
#' type = type_ridge(gradient = TRUE))
#'
#' ## use the joint.max argument to vary the maximum density used for
#' ## determining relative scaling...
#'
#' ## scaling of maximum density: jointly across all densities (default) vs. per facet
#' tinyplot(cyl ~ mpg, facet = ~ am, data = mtcars, type = "ridge", scale = 1)
#' tinyplot(cyl ~ mpg, facet = ~ am, data = mtcars, type = "ridge", scale = 1,
#' joint.max = "facet")
#' # jointly across all densities (default) vs. per facet
#' tinyplot(Month ~ Temp, facet = ~ Late, data = aq,
#' type = type_ridge(scale = 1))
#' tinyplot(Month ~ Temp, facet = ~ Late, data = aq,
#' type = type_ridge(scale = 1, joint.max = "facet"))
#'
#' ## scaling of maximum density: jointly across all densities (default) vs. per by row
#' tinyplot(am ~ mpg | factor(cyl), data = mtcars, type = "ridge", scale = 1)
#' tinyplot(am ~ mpg | factor(cyl), data = mtcars, type = "ridge", scale = 1,
#' joint.max = "by")
#' # jointly across all densities (default) vs. per by row
#' tinyplot(Month ~ Temp | Late, data = aq,
#' type = type_ridge(scale = 1))
#' tinyplot(Month ~ Temp | Late, data = aq,
#' type = type_ridge(scale = 1, joint.max = "by"))
#'
#' # restore the default theme
#' tinytheme()
#'
#' @export
type_ridge = function(
Expand Down Expand Up @@ -196,7 +220,6 @@ type_ridge = function(

out = list(
draw = draw_ridge(),
# data = data_ridge(),
data = data_ridge(bw = bw, adjust = adjust, kernel = kernel, n = n,
joint.bw = joint.bw,
scale = scale,
Expand Down Expand Up @@ -363,6 +386,8 @@ data_ridge = function(bw = "nrd0", adjust = 1, kernel = "gaussian", n = 512,
breaks[1L] = pmin(breaks[1L], xlim[1L])
breaks[length(breaks)] = pmax(breaks[length(breaks)], xlim[2L])
}

if (is.null(col) && (!anyby || x_by)) col = "black"

out = list(
datapoints = datapoints,
Expand Down Expand Up @@ -393,16 +418,28 @@ data_ridge = function(bw = "nrd0", adjust = 1, kernel = "gaussian", n = 512,
## Underlying draw_ridge function
draw_ridge = function() {
fun = function(ix, iy, iz, ibg, icol, iymin, iymax, type_info, ...) {
ridge_theme = identical(.tpar[["tinytheme"]], "ridge") || identical(.tpar[["tinytheme"]], "ridge2")
d = data.frame(x = ix, y = iy, ymin = iymin, ymax = iymax)
dsplit = split(d, d$y)
if (is.null(ibg)) {
default_bg = if (!is.null(.tpar[["palette.qualitative"]])) seq_palette(by_col(), n = 2)[2] else "gray"
default_bg = if (!ridge_theme && !is.null(.tpar[["palette.qualitative"]])) seq_palette(by_col(), n = 2)[2] else "gray"
ibg = if (isTRUE(type_info[["fill_by"]])) seq_palette(icol, n = 2)[2] else default_bg
}
if (!is.null(type_info[["alpha"]]) && is.null(type_info[["palette"]])) {
ibg = adjustcolor(ibg, alpha.f = type_info[["alpha"]])
}
if (!is.null(type_info[["col"]])) icol = type_info[["col"]]
lab = if (is.factor(d$y)) levels(d$y) else unique(d$y)
if (isTRUE(type_info[["y_by"]])) {
# avoid duplicating the y-axis labs for the special y==by case
# val = match(lab, levels(d$y)) - 1
val = match(d$y[1], levels(d$y))
lab = lab[val]
val = val - 1
} else {
val = cumsum(rep(1, length(lab))) - 1
}
if (ridge_theme) abline(h = val, col = .tpar[["grid.col"]])
draw_segments = if (type_info[["raster"]]) segmented_raster else segmented_polygon
for (i in rev(seq_along(dsplit))) {
if (type_info[["gradient"]]) {
Expand All @@ -422,17 +459,16 @@ draw_ridge = function() {
with(dsplit[[i]], polygon(x, ymax, col = if (type_info[["gradient"]]) "transparent" else ibg, border = NA))
with(dsplit[[i]], lines(x, ymax, col = icol))
}
lab = if (is.factor(d$y)) levels(d$y) else unique(d$y)
if (isTRUE(type_info[["y_by"]])) {
# avoid duplicating the y-axis labs for the special y==by case
# val = match(lab, levels(d$y)) - 1
val = match(d$y[1], levels(d$y))
lab = lab[val]
val = val - 1
# tinyAxis(x = d$y, side = 2, at = val, labels = lab, type = type_info[["yaxt"]], padj = padj)
if (ridge_theme) {
tinyAxis(x = d$y, side = 2, at = val, labels = lab, type = type_info[["yaxt"]],
padj = 0,
mgp = c(3, 1, 0) - c(0.5, 0.5 + 0.3, 0),
tcl = 0)
if (identical(.tpar[["tinytheme"]], "ridge2")) axis(1, labels = FALSE)
} else {
val = cumsum(rep(1, length(lab))) - 1
tinyAxis(x = d$y, side = 2, at = val, labels = lab, type = type_info[["yaxt"]])
}
tinyAxis(x = d$y, side = 2, at = val, labels = lab, type = type_info[["yaxt"]])
}
return(fun)
}
Expand Down
Loading