Skip to content
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
11 changes: 7 additions & 4 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ where the formatting is also better._

### Bug fixes

- Jittered plots now support Date/POSIXt axes. Thanks to @wachtermh for the bug
report and @vincentarelbundock for the code contribution. (#327)
- `tinyplot_add(type = "jitter")` no longer errors when layered on top of
boxplot, violin, or similar categorical plot types. (#560 @grantmcdermott)
- Several improvements/fixes to jittered plots and layering:
- Jittered plots now support Date/POSIXt axes. Thanks to @wachtermh for the
bug report and @vincentarelbundock for the code contribution. (#327)
- `tinyplot_add(type = "jitter")` no longer errors when layered on top of
boxplot, violin, or similar categorical plot types. (#560 @grantmcdermott)
- Jitter layers added via `tinyplot_add()` now align correctly with grouped
(offset) boxplot, violin, and ridge base layers. (#493 @grantmcdermott)

### Internals

Expand Down
9 changes: 7 additions & 2 deletions R/tinyplot.R
Original file line number Diff line number Diff line change
Expand Up @@ -795,10 +795,13 @@ tinyplot.default = function(
ribbon.alpha = sanitize_ribbon_alpha(NULL),

# misc
flip = flip,
dodge = NULL,
add = add,
by = by,
dodge = NULL,
dots = dots,
flip = flip,
group_offsets = NULL,
offsets_axis = NULL,
type_info = list() # pass type-specific info from type_data to type_draw
)

Expand Down Expand Up @@ -873,6 +876,8 @@ tinyplot.default = function(
# ensure axis aligment of any added layers
if (!add) {
assign("xlabs_orig", settings[["xlabs"]], envir = get(".tinyplot_env", envir = parent.env(environment())))
assign(".group_offsets", settings[["group_offsets"]], envir = get(".tinyplot_env", envir = parent.env(environment())))
assign(".offsets_axis", settings[["offsets_axis"]], envir = get(".tinyplot_env", envir = parent.env(environment())))
} else {
align_layer(settings)
}
Expand Down
35 changes: 22 additions & 13 deletions R/type_boxplot.R
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type_boxplot = function(
boxwex = boxwex,
staplewex = staplewex,
outwex = outwex),
data = data_boxplot(),
data = data_boxplot(boxwex = boxwex),
name = "boxplot"
)
class(out) = "tinyplot_type"
Expand All @@ -60,13 +60,9 @@ draw_boxplot = function(range, width, varwidth, notch, outline, boxwex, staplewe

# Handle multiple groups
if (ngrps > 1 && isFALSE(x_by) && isFALSE(facet_by)) {
boxwex_orig = boxwex
group_offsets = get_environment_variable(".group_offsets")
boxwex = boxwex / ngrps - 0.01
at_ix = at_ix + seq(
-((boxwex_orig - boxwex) / 2),
((boxwex_orig - boxwex) / 2),
length.out = ngrps
)[iby]
at_ix = at_ix + group_offsets[iby]
}

boxplot(
Expand All @@ -93,7 +89,7 @@ draw_boxplot = function(range, width, varwidth, notch, outline, boxwex, staplewe



data_boxplot = function() {
data_boxplot = function(boxwex = 0.8) {
fun = function(settings, ...) {
env2env(settings, environment(), c("datapoints", "by", "facet", "null_facet", "null_palette", "x", "col", "bg", "null_by"))
# Convert x to factor if it's not already
Expand Down Expand Up @@ -134,6 +130,20 @@ data_boxplot = function() {
by = if (length(unique(datapoints$by)) > 1) datapoints$by else by
facet = if (length(unique(datapoints$facet)) > 1) datapoints$facet else facet

# Compute group offsets for multi-group boxplots
ngrps = length(unique(datapoints$by))
if (ngrps > 1 && !settings$x_by) {
boxwex_grp = boxwex / ngrps - 0.01
group_offsets = seq(
-((boxwex - boxwex_grp) / 2),
((boxwex - boxwex_grp) / 2),
length.out = ngrps
)
} else {
group_offsets = rep(0, max(ngrps, 1))
}
offsets_axis = "x"

# legend customizations
settings$legend_args[["pch"]] = settings$legend_args[["pch"]] %||% 22
settings$legend_args[["pt.cex"]] = settings$legend_args[["pt.cex"]] %||% 3.5
Expand All @@ -150,11 +160,10 @@ data_boxplot = function() {
"col",
"bg",
"by",
"facet"))
"facet",
"group_offsets",
"offsets_axis"
))
}
return(fun)
}




31 changes: 30 additions & 1 deletion R/type_jitter.R
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jitter_restore = function(obj, factor, amount) {

data_jitter = function(factor, amount) {
fun = function(settings, ...) {
env2env(settings, environment(), "datapoints")
env2env(settings, environment(), c("datapoints", "add"))

x = datapoints$x
y = datapoints$y
Expand All @@ -60,6 +60,35 @@ data_jitter = function(factor, amount) {
} else {
ylabs = NULL
}

# Apply group offsets from base layer (e.g., boxplot, violin, ridge)
group_offsets = get_environment_variable(".group_offsets")
offsets_axis = get_environment_variable(".offsets_axis")
if (isTRUE(add) && !is.null(group_offsets)) {
if (identical(offsets_axis, "x") && is.factor(datapoints$by)) {
# x-axis offsets (boxplot, violin): keyed by group level
if (is.null(xlabs)) {
xf = as.factor(x)
xlvls = levels(xf)
xlabs = seq_along(xlvls)
names(xlabs) = xlvls
x = as.integer(xf)
}
x = x + group_offsets[as.integer(datapoints$by)]
} else if (identical(offsets_axis, "y")) {
# y-axis offsets (ridge): keyed by y-level name
if (is.null(ylabs)) {
yf = as.factor(y)
ylvls = levels(yf)
ylabs = seq_along(ylvls)
names(ylabs) = ylvls
y = as.integer(yf)
}
y_labels = names(ylabs)[y]
y = group_offsets[y_labels]
}
}

x = jitter_restore(x, factor = factor, amount = amount)
y = jitter_restore(y, factor = factor, amount = amount)

Expand Down
9 changes: 8 additions & 1 deletion R/type_ridge.R
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,11 @@ data_ridge = function(bw = "nrd0", adjust = 1, kernel = "gaussian", n = 512,
}
datapoints = do.call(rbind, lapply(datapoints, offset_z))

# Store y-level offsets for added layers (e.g., jitter)
ylevs = unique(datapoints$y)
group_offsets = setNames(seq_along(ylevs) - 1L, ylevs)
offsets_axis = "y"

if (y_by) {
datapoints$y = factor(datapoints$y)
datapoints$by = factor(datapoints$y, levels = rev(levels(datapoints$y)))
Expand Down Expand Up @@ -420,7 +425,9 @@ data_ridge = function(bw = "nrd0", adjust = 1, kernel = "gaussian", n = 512,
"datapoints",
"yaxt",
"ylim",
"type_info"
"type_info",
"group_offsets",
"offsets_axis"
))
}
return(fun)
Expand Down
20 changes: 17 additions & 3 deletions R/type_violin.R
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,19 @@ data_violin = function(bw = "nrd0", adjust = 1, kernel = "gaussian", n = 512,
}
}

# Compute group offsets for multi-group violins
if (ngrps > 1 && isFALSE(x_by) && isFALSE(facet_by)) {
xwidth_grp = width / ngrps - 0.01
group_offsets = seq(
-((width - xwidth_grp) / 2),
((width - xwidth_grp) / 2),
length.out = ngrps
)
} else {
group_offsets = rep(0, max(ngrps, 1))
}
offsets_axis = "x"

datapoints = lapply(seq_along(datapoints), function(d) {
dat = datapoints[[d]]
if (trim) {
Expand Down Expand Up @@ -182,7 +195,7 @@ data_violin = function(bw = "nrd0", adjust = 1, kernel = "gaussian", n = 512,
xwidth = xwidth_orig / ngrps - 0.01
x = rescale_num(x, to = c(0, xwidth))
x = x + as.numeric(sub("^([0-9]+)\\..*", "\\1", names(datapoints)[d])) - xwidth/2
x = x + seq(-((xwidth_orig - xwidth) / 2), ((xwidth_orig - xwidth) / 2), length.out = ngrps)[dat$by[1]]
x = x + group_offsets[dat$by[1]]
} else if (nfacets > 1) {
x = rescale_num(x, to = c(0, xwidth))
x = x + as.numeric(sub("^([0-9]+)\\..*", "\\1", names(datapoints)[d])) - xwidth/2
Expand Down Expand Up @@ -221,9 +234,10 @@ data_violin = function(bw = "nrd0", adjust = 1, kernel = "gaussian", n = 512,
"ylab",
"xlabs",
"col",
"bg"
"bg",
"group_offsets",
"offsets_axis"
))
}
return(fun)
}

Loading
Loading