diff --git a/R/draw_legend.R b/R/draw_legend.R index ce960e6b..3d7abd4e 100644 --- a/R/draw_legend.R +++ b/R/draw_legend.R @@ -355,38 +355,20 @@ draw_legend = function( # Legend drawing is handled by the internal `tinylegend()` function, which: # 1. calculates appropriate insets for "outer" legend placement # 2. can draw gradient legends (via `gradient_legend()` below) - # - # Note: We wrap everything in `recordGraphics()` to preserve legend spacing - # if the plot is resized (also necessary for Positron graphics logic regardless) - recordGraphics( - tinylegend( - legend_args = legend_args, - ooma = ooma, - omar = omar, - lmar = lmar, - topmar_epsilon = topmar_epsilon, - outer_side = outer_side, - outer_right = outer_right, - outer_end = outer_end, - outer_bottom = outer_bottom, - gradient = gradient, - draw = draw - ), - list = list( - legend_args = legend_args, - ooma = ooma, - omar = omar, - lmar = lmar, - topmar_epsilon = topmar_epsilon, - outer_side = outer_side, - outer_right = outer_right, - outer_end = outer_end, - outer_bottom = outer_bottom, - gradient = gradient, - draw = draw - ), - env = getNamespace("tinyplot") - ) + + tinylegend( + legend_args = legend_args, + ooma = ooma, + omar = omar, + lmar = lmar, + topmar_epsilon = topmar_epsilon, + outer_side = outer_side, + outer_right = outer_right, + outer_end = outer_end, + outer_bottom = outer_bottom, + gradient = gradient, + draw = draw + ) } diff --git a/R/tinyplot.R b/R/tinyplot.R index 27954b73..1a500a86 100644 --- a/R/tinyplot.R +++ b/R/tinyplot.R @@ -603,9 +603,26 @@ tinyplot.default = function( asp = NA, ...) { + dots = list(...) + par_first = get_saved_par("first") if (is.null(par_first)) set_saved_par("first", par()) + # Write plot to output file or window with fixed dimensions + setup_device(file = file, width = width, height = height) + if (!is.null(file)) on.exit(dev.off(), add = TRUE) + + # Save current graphical parameters + opar = par(no.readonly = TRUE) + if (restore.par || !is.null(facet)) { + if (!is.null(file) || !is.null(width) || !is.null(height)) { + opar$new = FALSE # catch for some interfaces + } + on.exit(par(opar), add = TRUE) + } + # set_orig_par(opar) + set_saved_par(when = "before", opar) + assert_logical(add) # save for tinyplot_add() @@ -620,12 +637,9 @@ tinyplot.default = function( # assign(".last_call", cal, envir = get(".tinyplot_env", envir = parent.env(environment()))) } - dots = list(...) - if (add) legend = FALSE draw = substitute(draw) - # sanitize arguments # type factories vs. strings @@ -637,6 +651,48 @@ tinyplot.default = function( type_draw = type$draw type = type$name + # Capture deparsed expressions early, before x, y and by are evaluated + x_dep = if (!is.null(x)) { + deparse1(substitute(x)) + } else if (type %in% c("rect", "segments")) { + x = NULL + NULL + } + y_dep = if (is.null(y)) { + deparse1(substitute(x)) + } else { + deparse1(substitute(y)) + } + by_dep = deparse1(substitute(by)) + null_by = is.null(by) + + ## coerce character variables to factors + if (!is.null(x) && is.character(x)) x = factor(x) + if (!is.null(y) && is.character(y)) y = factor(y) + if (!null_by && is.character(by)) by = factor(by) + + # flag if x==by (currently only used for "boxplot", "spineplot" and "ridges" types) + x_by = identical(x, by) + + facet_dep = deparse1(substitute(facet)) + # flag if facet==by + facet_by = FALSE + if (!is.null(facet) && length(facet) == 1 && facet == "by") { + by = as.factor(by) ## if by==facet, then both need to be factors + facet = by + facet_by = TRUE + } else if (!is.null(facet) && inherits(facet, "formula")) { + facet = get_facet_fml(facet, data = data) + if (isTRUE(attr(facet, "facet_grid"))) { + facet.args[["nrow"]] = attr(facet, "facet_nrow") + } + } + + ymin_dep = deparse(substitute(ymin)) + ymax_dep = deparse(substitute(ymax)) + xmin_dep = deparse(substitute(xmin)) + xmax_dep = deparse(substitute(xmax)) + # area flag (mostly for legend) was_area_type = identical(type, "area") # check flip flag is logical @@ -679,75 +735,12 @@ tinyplot.default = function( axes = any(c(xaxt, yaxt) != "n") if (is.null(frame.plot) || !is.logical(frame.plot)) frame.plot = all(c(xaxt, yaxt) %in% c("s", "a")) - # Write plot to output file or window with fixed dimensions - setup_device(file = file, width = width, height = height) - if (!is.null(file)) on.exit(dev.off(), add = TRUE) - - # Save current graphical parameters - opar = par(no.readonly = TRUE) - if (restore.par || !is.null(facet)) { - if (!is.null(file) || !is.null(width) || !is.null(height)) { - opar$new = FALSE # catch for some interfaces - } - on.exit(par(opar), add = TRUE) - } - # set_orig_par(opar) - set_saved_par(when = "before", opar) - - # catch for adding to existing facet plot - if (!is.null(facet) && add) { - if (Sys.getenv("POSITRON") == 1 && interactive()) warning( - "Positron IDE detected.\n", - "Adding layers to a faceted tinyplot in Positron leads to known alignment errors. Please see:\n", - "https://github.com/posit-dev/positron/issues/7316" - ) - par(get_saved_par(when = "after")) - } - - # Capture deparsed expressions early, before x, y and by are evaluated - x_dep = if (!is.null(x)) { - deparse1(substitute(x)) - } else if (type %in% c("rect", "segments")) { - x = NULL - NULL - } - y_dep = if (is.null(y)) { - deparse1(substitute(x)) - } else { - deparse1(substitute(y)) - } - by_dep = deparse1(substitute(by)) - null_by = is.null(by) - - ## coerce character variables to factors - if (!is.null(x) && is.character(x)) x = factor(x) - if (!is.null(y) && is.character(y)) y = factor(y) - if (!null_by && is.character(by)) by = factor(by) - - # flag if x==by (currently only used for "boxplot", "spineplot" and "ridges" types) - x_by = identical(x, by) - - facet_dep = deparse1(substitute(facet)) - # flag if facet==by - facet_by = FALSE - if (!is.null(facet) && length(facet) == 1 && facet == "by") { - by = as.factor(by) ## if by==facet, then both need to be factors - facet = by - facet_by = TRUE - } else if (!is.null(facet) && inherits(facet, "formula")) { - facet = get_facet_fml(facet, data = data) - if (isTRUE(attr(facet, "facet_grid"))) { - facet.args[["nrow"]] = attr(facet, "facet_nrow") - } - } facet_attr = attributes(facet) ## TODO: better solution for restoring facet attributes? null_facet = is.null(facet) if (is.null(x)) { ## Special catch for rect and segment plots without a specified y-var if (type %in% c("rect", "segments")) { - xmin_dep = deparse(substitute(xmin)) - xmax_dep = deparse(substitute(xmax)) x_dep = paste0("[", xmin_dep, ", ", xmax_dep, "]") x = rep(NA, length(x)) } @@ -755,8 +748,6 @@ tinyplot.default = function( if (is.null(y)) { ## Special catch for area and interval plots without a specified y-var if (type %in% c("rect", "segments", "pointrange", "errorbar", "ribbon")) { - ymin_dep = deparse(substitute(ymin)) - ymax_dep = deparse(substitute(ymax)) y_dep = paste0("[", ymin_dep, ", ", ymax_dep, "]") y = rep(NA, length(x)) } else if (type == "density") { @@ -790,583 +781,639 @@ tinyplot.default = function( datapoints[["by"]] = if (!null_by) by else "" } - ## initialize empty list with information that type_data - ## can overwrite in order to pass on to type_draw - type_info = list() - - if (!is.null(type_data)) { - fargs = list( - datapoints = datapoints, - bg = bg, - by = by, - col = col, - log = log, - lty = lty, - lwd = lwd, - facet = facet, - facet_by = facet_by, - facet.args = facet.args, - null_by = null_by, - null_facet = null_facet, - palette = palette, - ribbon.alpha = ribbon.alpha, - xaxt = xaxt, - xaxb = xaxb, - xaxl = xaxl, - xlab = xlab, - xlabs = xlabs, - xlim = xlim, - yaxt = yaxt, - yaxb = yaxb, - yaxl = yaxl, - ylab = ylab, - ylim = ylim - ) - fargs = c(fargs, dots) - list2env(do.call(type_data, fargs), environment()) - } - - - # swap x and y values if flip is TRUE - assert_flag(flip) - # extra catch for boxplots - # now swap the values - if (isTRUE(flip)) { - if (type != "boxplot") { - # limits, labs, etc. - xlim_cp = xlim - xlim = ylim - ylim = xlim_cp - xlab_cp = xlab - xlab = ylab - ylab = xlab_cp - xlabs_cp = xlabs - xlabs = ylabs - ylabs = xlabs_cp - xaxt_cp = xaxt - xaxt = yaxt - yaxt = xaxt_cp - xaxs_cp = xaxs - xaxs = yaxs - yaxs = xaxs_cp - xaxb_cp = xaxb - xaxb = yaxb - yaxb = xaxb_cp - xaxl_cp = xaxl - xaxl = yaxl - yaxl = xaxl_cp - if (!is.null(log)) { - log = if (log == "x") "y" else if (log == "y") "x" else log - } - # x/y vars - x_cp = datapoints[["x"]] - datapoints[["x"]] = datapoints[["y"]] - datapoints[["y"]] = x_cp - # x/y min and max vars - xmin_cp = if (!is.null(datapoints[["xmin"]])) datapoints[["xmin"]] else NULL - datapoints[["xmin"]] = if (!is.null(datapoints[["ymin"]])) datapoints[["ymin"]] else NULL - datapoints[["ymin"]] = if (!is.null(xmin_cp)) xmin_cp else NULL - xmax_cp = if (!is.null(datapoints[["xmax"]])) datapoints[["xmax"]] else NULL - datapoints[["xmax"]] = if (!is.null(datapoints[["ymax"]])) datapoints[["ymax"]] else NULL - datapoints[["ymax"]] = if (!is.null(xmax_cp)) xmax_cp else NULL - # clean up - rm(xlim_cp, xlab_cp, xlabs_cp, xaxt_cp, xaxs_cp, xaxb_cp, xaxl_cp, x_cp, xmin_cp, xmax_cp) - } else { - # We'll let boxplot(..., horizontal = TRUE) handle most of the adjustments - # and just catch a few elements that we draw beforehand. - xlab_cp = xlab - xlab = ylab - ylab = xlab_cp - rm(xlab_cp) - } + if (!exists("legend_args")) { + legend_args = dots[["legend_args"]] } + if (is.null(legend_args)) legend_args = list(x = NULL) + legend = substitute(legend) - # For cases where x/yaxb is provided and corresponding x/ylabs is not null... - # We can subset these here to provide breaks - if (!is.null(xaxb) && !is.null(xlabs)) { - xlabs = xlabs[names(xlabs) %in% xaxb] - xaxb = NULL # don't need this any more - } - if (!is.null(yaxb) && !is.null(ylabs)) { - ylabs = ylabs[names(ylabs) %in% yaxb] - yaxb = NULL # don't need this any more - } + # + ## main drawing logic ---- - # plot limits - fargs = lim_args( - datapoints = datapoints, - xlim = xlim, ylim = ylim, - xaxb = xaxb, yaxb = yaxb, - xlim_user = xlim_user, ylim_user = ylim_user, - type = type - ) - list2env(fargs, environment()) - - - # split data - by_ordered = FALSE - by_continuous = !null_by && inherits(datapoints$by, c("numeric", "integer")) - if (isTRUE(by_continuous) && type %in% c("l", "b", "o", "ribbon", "polygon", "polypath", "boxplot")) { - warning("\nContinuous legends not supported for this plot type. Reverting to discrete legend.") - by_continuous = FALSE - } else if (!null_by) { - by_ordered = is.ordered(by) - } - - if (length(unique(datapoints$facet)) == 1) { - datapoints[["facet"]] = NULL - } - if (!is.null(datapoints$facet)) { - split_data = split(datapoints, datapoints$facet) - split_data = lapply(split_data, as.list) - } else { - split_data = list(as.list(datapoints)) - } - - # aesthetics by group: col, bg, etc. - ngrps = if (null_by) 1L else if (is.factor(by)) length(levels(by)) else if (by_continuous) 100L else length(unique(by)) - pch = by_pch(ngrps = ngrps, type = type, pch = pch) - lty = by_lty(ngrps = ngrps, type = type, lty = lty) - lwd = by_lwd(ngrps = ngrps, type = type, lwd = lwd) - col = by_col( - ngrps = ngrps, col = col, palette = palette, - gradient = by_continuous, ordered = by_ordered, alpha = alpha) - bg = by_bg( - adjustcolor = adjustcolor, alpha = alpha, bg = bg, by = by, by_continuous = by_continuous, - by_ordered = by_ordered, col = col, fill = fill, palette = substitute(palette), - ribbon.alpha = ribbon.alpha, ngrps = ngrps, type = type) + # Note: We wrap everything in `recordGraphics()` to preserve spacing of + # dyanmic objects and measurements, e.g. if the plot is resized. - ncolors = length(col) - lgnd_labs = rep(NA, times = ncolors) - if (isTRUE(by_continuous)) { - ## Identify the pretty break points for our labels - nlabs = 5 - ncolors = length(col) - ubyvar = unique(by) - byvar_range = range(ubyvar) - pbyvar = pretty(byvar_range, n = nlabs) - pbyvar = pbyvar[pbyvar >= byvar_range[1] & pbyvar <= byvar_range[2]] - # optional thinning - if (length(ubyvar) == 2 && all(ubyvar %in% pbyvar)) { - pbyvar = ubyvar - } else if (length(pbyvar) > nlabs) { - pbyvar = pbyvar[seq_along(pbyvar) %% 2 == 0] - } - ## Find the (approximate) location of our pretty labels - pidx = rescale_num(c(byvar_range, pbyvar), to = c(1, ncolors))[-c(1:2)] - pidx = round(pidx) - lgnd_labs[pidx] = pbyvar - } - - # Determine the number and arrangement of facets. - # Note: We're do this up front, so we can make some adjustments to legend cex - # next (if there are facets). But the actual drawing of the facets will only - # come later. - attributes(datapoints$facet) = facet_attr ## TODO: better solution for restoring facet attributes? - fargs = facet_layout(facet = datapoints$facet, facet.args = facet.args, add = add) - list2env(fargs, environment()) - - # - ## Global plot elements (legend and titles) - # + recordGraphics( + + { + + # catch for adding to existing facet plot + if (!is.null(facet) && add) { + par(get_saved_par(when = "after")) + } + + ## initialize empty list with information that type_data + ## can overwrite in order to pass on to type_draw + type_info = list() + + if (!is.null(type_data)) { + fargs = list( + datapoints = datapoints, + bg = bg, + by = by, + col = col, + log = log, + lty = lty, + lwd = lwd, + facet = facet, + facet_by = facet_by, + facet.args = facet.args, + null_by = null_by, + null_facet = null_facet, + palette = palette, + ribbon.alpha = ribbon.alpha, + xaxt = xaxt, + xaxb = xaxb, + xaxl = xaxl, + xlab = xlab, + xlabs = xlabs, + xlim = xlim, + yaxt = yaxt, + yaxb = yaxb, + yaxl = yaxl, + ylab = ylab, + ylim = ylim + ) + fargs = c(fargs, dots) + list2env(do.call(type_data, fargs), environment()) + } - # place and draw the legend - has_legend = FALSE # simple indicator variable for later use - if (!exists("legend_args")) { - legend_args = dots[["legend_args"]] - } - if (is.null(legend_args)) legend_args = list(x = NULL) - legend = substitute(legend) + # swap x and y values if flip is TRUE + assert_flag(flip) + # extra catch for boxplots + # now swap the values + if (isTRUE(flip)) { + if (type != "boxplot") { + # limits, labs, etc. + xlim_cp = xlim + xlim = ylim + ylim = xlim_cp + xlab_cp = xlab + xlab = ylab + ylab = xlab_cp + xlabs_cp = xlabs + xlabs = ylabs + ylabs = xlabs_cp + xaxt_cp = xaxt + xaxt = yaxt + yaxt = xaxt_cp + xaxs_cp = xaxs + xaxs = yaxs + yaxs = xaxs_cp + xaxb_cp = xaxb + xaxb = yaxb + yaxb = xaxb_cp + xaxl_cp = xaxl + xaxl = yaxl + yaxl = xaxl_cp + if (!is.null(log)) { + log = if (log == "x") "y" else if (log == "y") "x" else log + } + # x/y vars + x_cp = datapoints[["x"]] + datapoints[["x"]] = datapoints[["y"]] + datapoints[["y"]] = x_cp + # x/y min and max vars + xmin_cp = if (!is.null(datapoints[["xmin"]])) datapoints[["xmin"]] else NULL + datapoints[["xmin"]] = if (!is.null(datapoints[["ymin"]])) datapoints[["ymin"]] else NULL + datapoints[["ymin"]] = if (!is.null(xmin_cp)) xmin_cp else NULL + xmax_cp = if (!is.null(datapoints[["xmax"]])) datapoints[["xmax"]] else NULL + datapoints[["xmax"]] = if (!is.null(datapoints[["ymax"]])) datapoints[["ymax"]] else NULL + datapoints[["ymax"]] = if (!is.null(xmax_cp)) xmax_cp else NULL + # clean up + rm(xlim_cp, xlab_cp, xlabs_cp, xaxt_cp, xaxs_cp, xaxb_cp, xaxl_cp, x_cp, xmin_cp, xmax_cp) + } else { + # We'll let boxplot(..., horizontal = TRUE) handle most of the adjustments + # and just catch a few elements that we draw beforehand. + xlab_cp = xlab + xlab = ylab + ylab = xlab_cp + rm(xlab_cp) + } + } + + # For cases where x/yaxb is provided and corresponding x/ylabs is not null... + # We can subset these here to provide breaks + if (!is.null(xaxb) && !is.null(xlabs)) { + xlabs = xlabs[names(xlabs) %in% xaxb] + xaxb = NULL # don't need this any more + } + if (!is.null(yaxb) && !is.null(ylabs)) { + ylabs = ylabs[names(ylabs) %in% yaxb] + yaxb = NULL # don't need this any more + } + + # plot limits + fargs = lim_args( + datapoints = datapoints, + xlim = xlim, ylim = ylim, + xaxb = xaxb, yaxb = yaxb, + xlim_user = xlim_user, ylim_user = ylim_user, + type = type + ) + list2env(fargs, environment()) - if (isFALSE(legend)) { - legend = "none" - } else if (isTRUE(legend)) { - legend = NULL - } - if (!is.null(legend) && legend == "none") { - legend_args[["x"]] = "none" - } - if (null_by) { - if (is.null(legend)) { - legend = "none" - legend_args[["x"]] = "none" - } - } + # split data + by_ordered = FALSE + by_continuous = !null_by && inherits(datapoints$by, c("numeric", "integer")) + if (isTRUE(by_continuous) && type %in% c("l", "b", "o", "ribbon", "polygon", "polypath", "boxplot")) { + warning("\nContinuous legends not supported for this plot type. Reverting to discrete legend.") + by_continuous = FALSE + } else if (!null_by) { + by_ordered = is.ordered(by) + } - if ((is.null(legend) || legend != "none") && !add) { - if (isFALSE(by_continuous)) { - if (ngrps > 1) { - lgnd_labs = if (is.factor(datapoints$by)) levels(datapoints$by) else unique(datapoints$by) + if (length(unique(datapoints$facet)) == 1) { + datapoints[["facet"]] = NULL + } + if (!is.null(datapoints$facet)) { + split_data = split(datapoints, datapoints$facet) + split_data = lapply(split_data, as.list) } else { - lgnd_labs = ylab + split_data = list(as.list(datapoints)) } - } - has_sub = !is.null(sub) - - if (isTRUE(was_area_type) || isTRUE(type %in% c("area", "rect", "hist", "histogram"))) { - legend_args[["pt.lwd"]] = par("lwd") - legend_args[["lty"]] = 0 - } + # aesthetics by group: col, bg, etc. + ngrps = if (null_by) 1L else if (is.factor(by)) length(levels(by)) else if (by_continuous) 100L else length(unique(by)) + pch = by_pch(ngrps = ngrps, type = type, pch = pch) + lty = by_lty(ngrps = ngrps, type = type, lty = lty) + lwd = by_lwd(ngrps = ngrps, type = type, lwd = lwd) + col = by_col( + ngrps = ngrps, col = col, palette = palette, + gradient = by_continuous, ordered = by_ordered, alpha = alpha) + bg = by_bg( + adjustcolor = adjustcolor, alpha = alpha, bg = bg, by = by, by_continuous = by_continuous, + by_ordered = by_ordered, col = col, fill = fill, palette = substitute(palette), + ribbon.alpha = ribbon.alpha, ngrps = ngrps, type = type) + + ncolors = length(col) + lgnd_labs = rep(NA, times = ncolors) + if (isTRUE(by_continuous)) { + ## Identify the pretty break points for our labels + nlabs = 5 + ncolors = length(col) + ubyvar = unique(by) + byvar_range = range(ubyvar) + pbyvar = pretty(byvar_range, n = nlabs) + pbyvar = pbyvar[pbyvar >= byvar_range[1] & pbyvar <= byvar_range[2]] + # optional thinning + if (length(ubyvar) == 2 && all(ubyvar %in% pbyvar)) { + pbyvar = ubyvar + } else if (length(pbyvar) > nlabs) { + pbyvar = pbyvar[seq_along(pbyvar) %% 2 == 0] + } + ## Find the (approximate) location of our pretty labels + pidx = rescale_num(c(byvar_range, pbyvar), to = c(1, ncolors))[-c(1:2)] + pidx = round(pidx) + lgnd_labs[pidx] = pbyvar + } - draw_legend( - legend = legend, - legend_args = legend_args, - by_dep = by_dep, - lgnd_labs = lgnd_labs, - type = type, - pch = pch, - lty = lty, - lwd = lwd, - col = col, - bg = bg, - gradient = by_continuous, - cex = cex * cex_fct_adj, - has_sub = has_sub - ) + # Determine the number and arrangement of facets. + # Note: We're do this up front, so we can make some adjustments to legend cex + # next (if there are facets). But the actual drawing of the facets will only + # come later. + attributes(datapoints$facet) = facet_attr ## TODO: better solution for restoring facet attributes? + fargs = facet_layout(facet = datapoints$facet, facet.args = facet.args, add = add) + list2env(fargs, environment()) + + # + ## Global plot elements (legend and titles) + # + + # place and draw the legend + has_legend = FALSE # simple indicator variable for later use + + if (isFALSE(legend)) { + legend = "none" + } else if (isTRUE(legend)) { + legend = NULL + } + if (!is.null(legend) && legend == "none") { + legend_args[["x"]] = "none" + } - has_legend = TRUE - } else if (legend_args[["x"]] == "none" && !add) { - omar = par("mar") - ooma = par("oma") - topmar_epsilon = 0.1 - - # Catch to avoid recursive offsets, e.g. repeated tinyplot calls with - # "bottom!" legend position. - - ## restore inner margin defaults - ## (in case the plot region/margins were affected by the preceding tinyplot call) - if (any(ooma != 0)) { - if (ooma[1] != 0 && omar[1] == par("mgp")[1] + 1 * par("cex.lab")) omar[1] = 5.1 - if (ooma[2] != 0 && omar[2] == par("mgp")[1] + 1 * par("cex.lab")) omar[2] = 4.1 - if (ooma[3] == topmar_epsilon && omar[3] != 4.1) omar[3] = 4.1 - if (ooma[4] != 0 && omar[4] == 0) omar[4] = 2.1 - par(mar = omar) - } - ## restore outer margin defaults (with a catch for custom mfrow plots) - if (all(par("mfrow") == c(1, 1))) { - par(omd = c(0, 1, 0, 1)) - } + if (null_by) { + if (is.null(legend)) { + legend = "none" + legend_args[["x"]] = "none" + } + } - # clean up for now - rm(omar, ooma, topmar_epsilon) + if ((is.null(legend) || legend != "none") && !add) { + if (isFALSE(by_continuous)) { + if (ngrps > 1) { + lgnd_labs = if (is.factor(datapoints$by)) levels(datapoints$by) else unique(datapoints$by) + } else { + lgnd_labs = ylab + } + } - # Draw new plot - plot.new() - } + has_sub = !is.null(sub) - # Titles. Only draw these if add = FALSE - if (!add) { - # main title - # Note that we include a special catch for the main title if legend is - # "top!" (and main is specified in the first place). - legend_eval = tryCatch(eval(legend), error = function(e) NULL) - # Extra bit of footwork if user passed legend = legend(...) instead of - # legend = list(...), since the call environment is tricky - if (is.null(legend_eval)) { - legend_eval = tryCatch(paste0(legend)[[2]], error = function(e) NULL) - } + if (isTRUE(was_area_type) || isTRUE(type %in% c("area", "rect", "hist", "histogram"))) { + legend_args[["pt.lwd"]] = par("lwd") + legend_args[["lty"]] = 0 + } - adj_title = !is.null(legend) && (legend == "top!" || (!is.null(legend_args[["x"]]) && legend_args[["x"]] == "top!") || (is.list(legend_eval) && legend_eval[[1]] == "top!")) + draw_legend( + legend = legend, + legend_args = legend_args, + by_dep = by_dep, + lgnd_labs = lgnd_labs, + type = type, + pch = pch, + lty = lty, + lwd = lwd, + col = col, + bg = bg, + gradient = by_continuous, + cex = cex * cex_fct_adj, + has_sub = has_sub + ) - # For the "top!" legend case, bump main title up to make space for the - # legend beneath it: Take the normal main title line gap (i.e., 1.7 lines) - # and add the difference between original top margin and new one (i.e., - # which should equal the height of the new legend). Note that we also - # include a 0.1 epsilon bump, which we're using to reset the tinyplot - # window in case of recursive "top!" calls. (See draw_legend code.) + has_legend = TRUE + } else if (legend_args[["x"]] == "none" && !add) { + omar = par("mar") + ooma = par("oma") + topmar_epsilon = 0.1 + + # Catch to avoid recursive offsets, e.g. repeated tinyplot calls with + # "bottom!" legend position. + + ## restore inner margin defaults + ## (in case the plot region/margins were affected by the preceding tinyplot call) + if (any(ooma != 0)) { + if (ooma[1] != 0 && omar[1] == par("mgp")[1] + 1 * par("cex.lab")) omar[1] = 5.1 + if (ooma[2] != 0 && omar[2] == par("mgp")[1] + 1 * par("cex.lab")) omar[2] = 4.1 + if (ooma[3] == topmar_epsilon && omar[3] != 4.1) omar[3] = 4.1 + if (ooma[4] != 0 && omar[4] == 0) omar[4] = 2.1 + par(mar = omar) + } + ## restore outer margin defaults (with a catch for custom mfrow plots) + if (all(par("mfrow") == c(1, 1))) { + par(omd = c(0, 1, 0, 1)) + } - if (isTRUE(adj_title)) { - line_main = par("mar")[3] - opar[["mar"]][3] + 1.7 + 0.1 - } else { - line_main = NULL - } + # clean up for now + rm(omar, ooma, topmar_epsilon) - if (!is.null(sub)) { - if (isTRUE(get_tpar("side.sub", 1) == 3)) { - if (is.null(line_main)) line_main = par("mgp")[3] + 1.7 - .1 - line_main = line_main + 1.2 - } - if (isTRUE(get_tpar("side.sub", 1) == 3)) { - line_sub = get_tpar("line.sub", 1.7) - } else { - line_sub = get_tpar("line.sub", 4) + # Draw new plot + plot.new() } - args = list( - text = sub, - line = line_sub, - cex = get_tpar("cex.sub", 1.2), - col = get_tpar("col.sub", "black"), - adj = get_tpar(c("adj.sub", "adj")), - font = get_tpar("font.sub", 1), - side = get_tpar("side.sub", 1), - las = 1 - ) - args = Filter(function(x) !is.null(x), args) - do.call(mtext, args) - } - - if (!is.null(main)) { - args = list( - main = main, - line = line_main, - cex.main = get_tpar("cex.main", 1.4), - col.main = get_tpar("col.main", "black"), - font.main = get_tpar("font.main", 2), - adj = get_tpar(c("adj.main", "adj"), 3)) - args = Filter(function(x) !is.null(x), args) - do.call(title, args) - } + # Titles. Only draw these if add = FALSE + if (!add) { + # main title + # Note that we include a special catch for the main title if legend is + # "top!" (and main is specified in the first place). + legend_eval = tryCatch(eval(legend), error = function(e) NULL) + # Extra bit of footwork if user passed legend = legend(...) instead of + # legend = list(...), since the call environment is tricky + if (is.null(legend_eval)) { + legend_eval = tryCatch(paste0(legend)[[2]], error = function(e) NULL) + } - # Axis titles - args = list(xlab = xlab) - args[["adj"]] = get_tpar(c("adj.xlab", "adj")) - do.call(title, args) - args = list(ylab = ylab) - args[["adj"]] = get_tpar(c("adj.ylab", "adj")) - do.call(title, args) - } + adj_title = !is.null(legend) && (legend == "top!" || (!is.null(legend_args[["x"]]) && legend_args[["x"]] == "top!") || (is.list(legend_eval) && legend_eval[[1]] == "top!")) - # - ## Exterior plot elements (plot and facet windows, axes, etc.) - # + # For the "top!" legend case, bump main title up to make space for the + # legend beneath it: Take the normal main title line gap (i.e., 1.7 lines) + # and add the difference between original top margin and new one (i.e., + # which should equal the height of the new legend). Note that we also + # include a 0.1 epsilon bump, which we're using to reset the tinyplot + # window in case of recursive "top!" calls. (See draw_legend code.) - omar = NULL # Placeholder variable for now, which we re-assign as part of facet margins - - # placeholders for facet_window_args() call - facet_newlines = facet_text = facet_rect = facet_font = facet_col = facet_bg = facet_border = NULL - - if (!is.null(facet) && !add) { - if (is.null(omar)) omar = par("mar") - - # Grab some of the customizable facet args that we'll be using later - facet_rect = FALSE - facet_text = .tpar[["facet.cex"]] - facet_font = .tpar[["facet.font"]] - facet_col = .tpar[["facet.col"]] - facet_bg = .tpar[["facet.bg"]] - facet_border = .tpar[["facet.border"]] - if (!is.null(facet.args)) { - if (!is.null(facet.args[["cex"]])) facet_text = facet.args[["cex"]] - if (!is.null(facet.args[["col"]])) facet_col = facet.args[["col"]] - if (!is.null(facet.args[["font"]])) facet_font = facet.args[["font"]] - if (!is.null(facet.args[["bg"]])) facet_bg = facet.args[["bg"]] - if (!is.null(facet.args[["border"]])) facet_border = facet.args[["border"]] - } - if (!is.null(facet_bg) || !is.null(facet_border)) facet_rect = TRUE - - # Need extra adjustment to top margin if facet titles have "\n" newline - # separator. (Note that we'll also need to take account for this in the - # individual facet margins / gaps further below.) - facet_newlines = lengths(gregexpr("\n", grep("\\n", facets, value = TRUE))) - # if (length(facet_newlines)==0) facet_newlines = 0 - # omar[3] = omar[3] + max(facet_newlines) - facet_newlines = ifelse(length(facet_newlines) == 0, 0, max(facet_newlines)) - omar[3] = omar[3] + facet_newlines * facet_text / cex_fct_adj - # apply the changes - par(mar = omar) - } + if (isTRUE(adj_title)) { + line_main = par("mar")[3] - opar[["mar"]][3] + 1.7 + 0.1 + } else { + line_main = NULL + } - # Now draw the individual facet windows (incl. axes, grid lines, and facet titles) - # Will be skipped if adding to an existing plot; see ?facet + if (!is.null(sub)) { + if (isTRUE(get_tpar("side.sub", 1) == 3)) { + if (is.null(line_main)) line_main = par("mgp")[3] + 1.7 - .1 + line_main = line_main + 1.2 + } + if (isTRUE(get_tpar("side.sub", 1) == 3)) { + line_sub = get_tpar("line.sub", 1.7) + } else { + line_sub = get_tpar("line.sub", 4) + } + args = list( + text = sub, + line = line_sub, + cex = get_tpar("cex.sub", 1.2), + col = get_tpar("col.sub", "black"), + adj = get_tpar(c("adj.sub", "adj")), + font = get_tpar("font.sub", 1), + side = get_tpar("side.sub", 1), + las = 1 + ) + args = Filter(function(x) !is.null(x), args) + do.call(mtext, args) + } - facet_window_args = recordGraphics( - draw_facet_window( - add = add, - # facet-specific args - cex_fct_adj = cex_fct_adj, - facet.args = facet.args, - facet_newlines = facet_newlines, facet_font = facet_font, - facet_rect = facet_rect, facet_text = facet_text, - facet_col = facet_col, facet_bg = facet_bg, facet_border = facet_border, - facet = facet, - facets = facets, ifacet = ifacet, - nfacets = nfacets, nfacet_cols = nfacet_cols, nfacet_rows = nfacet_rows, - # axes args - axes = axes, flip = flip, frame.plot = frame.plot, - oxaxis = oxaxis, oyaxis = oyaxis, - xlabs = xlabs, xlim = xlim, xlim_user = xlim_user, xaxt = xaxt, xaxs = xaxs, xaxb = xaxb, xaxl = xaxl, - ylabs = ylabs, ylim = ylim, ylim_user = ylim_user, yaxt = yaxt, yaxs = yaxs, yaxb = yaxb, yaxl = yaxl, - asp = asp, log = log, - # other args (in approx. alphabetical + group ordering) - dots = dots, - draw = draw, - grid = grid, - has_legend = has_legend, - type = type, - x = x, xmax = xmax, xmin = xmin, - y = y, ymax = ymax, ymin = ymin - ), - list = list( - add = add, - cex_fct_adj = cex_fct_adj, - facet.args = facet.args, - facet_newlines = facet_newlines, facet_font = facet_font, - facet_rect = facet_rect, facet_text = facet_text, - facet_col = facet_col, facet_bg = facet_bg, facet_border = facet_border, - facet = datapoints$facet, - facets = facets, ifacet = ifacet, - nfacets = nfacets, nfacet_cols = nfacet_cols, nfacet_rows = nfacet_rows, - axes = axes, flip = flip, frame.plot = frame.plot, - oxaxis = oxaxis, oyaxis = oyaxis, - xlabs = xlabs, xlim = xlim, xlim_user = xlim_user, xaxt = xaxt, xaxs = xaxs, xaxb = xaxb, xaxl = xaxl, - ylabs = ylabs, ylim = ylim, ylim_user = ylim_user, yaxt = yaxt, yaxs = yaxs, yaxb = yaxb, yaxl = yaxl, - asp = asp, log = log, - dots = dots, - draw = draw, - grid = grid, - has_legend = has_legend, - type = type, - x = datapoints$x, xmax = datapoints$xmax, xmin = datapoints$xmin, - y = datapoints$y, ymax = datapoints$ymax, ymin = datapoints$ymin - ), - getNamespace("tinyplot") - ) - list2env(facet_window_args, environment()) + if (!is.null(main)) { + args = list( + main = main, + line = line_main, + cex.main = get_tpar("cex.main", 1.4), + col.main = get_tpar("col.main", "black"), + font.main = get_tpar("font.main", 2), + adj = get_tpar(c("adj.main", "adj"), 3)) + args = Filter(function(x) !is.null(x), args) + do.call(title, args) + } - # - ## Interior plot elements - # + # Axis titles + args = list(xlab = xlab) + args[["adj"]] = get_tpar(c("adj.xlab", "adj")) + do.call(title, args) + args = list(ylab = ylab) + args[["adj"]] = get_tpar(c("adj.ylab", "adj")) + do.call(title, args) + } - # Finally, we can draw all of the plot elements (points, lines, etc.) - # We'll do this via a nested loops: - # 1) Outer loop over facets - # 2) Inner loop over groups - - ## Outer loop over the facets - for (i in seq_along(split_data)) { - # Split group-level data again to grab any "by" groups - idata = split_data[[i]] - iby = idata[["by"]] - if (!null_by) { ## maybe all(iby=="") - if (isTRUE(by_continuous)) { - idata[["col"]] = col[round(rescale_num(idata$by, from = range(datapoints$by), to = c(1, 100)))] - idata[["bg"]] = bg[round(rescale_num(idata$by, from = range(datapoints$by), to = c(1, 100)))] - idata = list(idata) - } else { - idata = lapply(idata, split, iby) - idata = do.call(function(...) Map("list", ...), idata) + # + ## Exterior plot elements (plot and facet windows, axes, etc.) + # + + omar = NULL # Placeholder variable for now, which we re-assign as part of facet margins + + # placeholders for facet_window_args() call + facet_newlines = facet_text = facet_rect = facet_font = facet_col = facet_bg = facet_border = NULL + + if (!is.null(facet) && !add) { + if (is.null(omar)) omar = par("mar") + + # Grab some of the customizable facet args that we'll be using later + facet_rect = FALSE + facet_text = .tpar[["facet.cex"]] + facet_font = .tpar[["facet.font"]] + facet_col = .tpar[["facet.col"]] + facet_bg = .tpar[["facet.bg"]] + facet_border = .tpar[["facet.border"]] + if (!is.null(facet.args)) { + if (!is.null(facet.args[["cex"]])) facet_text = facet.args[["cex"]] + if (!is.null(facet.args[["col"]])) facet_col = facet.args[["col"]] + if (!is.null(facet.args[["font"]])) facet_font = facet.args[["font"]] + if (!is.null(facet.args[["bg"]])) facet_bg = facet.args[["bg"]] + if (!is.null(facet.args[["border"]])) facet_border = facet.args[["border"]] + } + if (!is.null(facet_bg) || !is.null(facet_border)) facet_rect = TRUE + + # Need extra adjustment to top margin if facet titles have "\n" newline + # separator. (Note that we'll also need to take account for this in the + # individual facet margins / gaps further below.) + facet_newlines = lengths(gregexpr("\n", grep("\\n", facets, value = TRUE))) + # if (length(facet_newlines)==0) facet_newlines = 0 + # omar[3] = omar[3] + max(facet_newlines) + facet_newlines = ifelse(length(facet_newlines) == 0, 0, max(facet_newlines)) + omar[3] = omar[3] + facet_newlines * facet_text / cex_fct_adj + # apply the changes + par(mar = omar) } - } else { - idata = list(idata) - if (isTRUE(by_continuous)) { - if (length(col) != 1) { - idata[[1]][["col"]] = col[round(rescale_num(by, to = c(1, 100)))] + + # Now draw the individual facet windows (incl. axes, grid lines, and facet titles) + # Will be skipped if adding to an existing plot; see ?facet + + facet_window_args = draw_facet_window( + add = add, + cex_fct_adj = cex_fct_adj, + facet.args = facet.args, + facet_newlines = facet_newlines, facet_font = facet_font, + facet_rect = facet_rect, facet_text = facet_text, + facet_col = facet_col, facet_bg = facet_bg, facet_border = facet_border, + facet = datapoints$facet, + facets = facets, ifacet = ifacet, + nfacets = nfacets, nfacet_cols = nfacet_cols, nfacet_rows = nfacet_rows, + axes = axes, flip = flip, frame.plot = frame.plot, + oxaxis = oxaxis, oyaxis = oyaxis, + xlabs = xlabs, xlim = xlim, xlim_user = xlim_user, xaxt = xaxt, xaxs = xaxs, xaxb = xaxb, xaxl = xaxl, + ylabs = ylabs, ylim = ylim, ylim_user = ylim_user, yaxt = yaxt, yaxs = yaxs, yaxb = yaxb, yaxl = yaxl, + asp = asp, log = log, + dots = dots, + draw = draw, + grid = grid, + has_legend = has_legend, + type = type, + x = datapoints$x, xmax = datapoints$xmax, xmin = datapoints$xmin, + y = datapoints$y, ymax = datapoints$ymax, ymin = datapoints$ymin + ) + list2env(facet_window_args, environment()) + + + # + ## Interior plot elements + # + + # Finally, we can draw all of the plot elements (points, lines, etc.) + # We'll do this via a nested loops: + # 1) Outer loop over facets + # 2) Inner loop over groups + + ## Outer loop over the facets + for (i in seq_along(split_data)) { + # Split group-level data again to grab any "by" groups + idata = split_data[[i]] + iby = idata[["by"]] + if (!null_by) { ## maybe all(iby=="") + if (isTRUE(by_continuous)) { + idata[["col"]] = col[round(rescale_num(idata$by, from = range(datapoints$by), to = c(1, 100)))] + idata[["bg"]] = bg[round(rescale_num(idata$by, from = range(datapoints$by), to = c(1, 100)))] + idata = list(idata) + } else { + idata = lapply(idata, split, iby) + idata = do.call(function(...) Map("list", ...), idata) + } } else { - idata[[1]][["col"]] = col + idata = list(idata) + if (isTRUE(by_continuous)) { + if (length(col) != 1) { + idata[[1]][["col"]] = col[round(rescale_num(by, to = c(1, 100)))] + } else { + idata[[1]][["col"]] = col + } + if (length(bg) != 1) { + idata[[1]][["bg"]] = bg[round(rescale_num(by, to = c(1, 100)))] + } else { + idata[[1]][["bg"]] = bg + } + } } - if (length(bg) != 1) { - idata[[1]][["bg"]] = bg[round(rescale_num(by, to = c(1, 100)))] - } else { - idata[[1]][["bg"]] = bg + + # Set the facet "window" manually + # See: https://github.com/grantmcdermott/tinyplot/issues/65 + if (nfacets > 1) { + mfgi = ceiling(i / nfacet_cols) + mfgj = i %% nfacet_cols + if (mfgj == 0) mfgj = nfacet_cols + par(mfg = c(mfgi, mfgj)) + + # For free facets, we need to reset par(usr) based extent of that + # particular facet... which we calculated and saved to the .fusr env var + # (list) back in draw_facet_window() + if (isTRUE(facet.args[["free"]])) { + fusr = get(".fusr", envir = get(".tinyplot_env", envir = parent.env(environment()))) + par(usr = fusr[[i]]) + } } - } - } - - # Set the facet "window" manually - # See: https://github.com/grantmcdermott/tinyplot/issues/65 - if (nfacets > 1) { - mfgi = ceiling(i / nfacet_cols) - mfgj = i %% nfacet_cols - if (mfgj == 0) mfgj = nfacet_cols - par(mfg = c(mfgi, mfgj)) - - # For free facets, we need to reset par(usr) based extent of that - # particular facet... which we calculated and saved to the .fusr env var - # (list) back in draw_facet_window() - if (isTRUE(facet.args[["free"]])) { - fusr = get(".fusr", envir = get(".tinyplot_env", envir = parent.env(environment()))) - par(usr = fusr[[i]]) - } - } - ## Inner loop over the "by" groupings - for (ii in seq_along(idata)) { - icol = col[ii] - ibg = bg[ii] - ipch = pch[ii] - ilty = lty[ii] - ilwd = lwd[ii] + ## Inner loop over the "by" groupings + for (ii in seq_along(idata)) { + icol = col[ii] + ibg = bg[ii] + ipch = pch[ii] + ilty = lty[ii] + ilwd = lwd[ii] + + ix = idata[[ii]][["x"]] + iy = idata[[ii]][["y"]] + iz = idata[[ii]][["z"]] + ixmin = idata[[ii]]$xmin + ixmax = idata[[ii]]$xmax + iymin = idata[[ii]]$ymin + iymax = idata[[ii]]$ymax + ilabels = idata[[ii]][["labels"]] + + if (isTRUE(by_continuous)) { + icol = idata[[ii]]$col + ibg = idata[[ii]]$bg + } + + # empty plot flag + empty_plot = FALSE + if (isTRUE(empty) || isTRUE(type == "n") || ((length(ix) == 0) && !(type %in% c("histogram", "hist", "rect", "segments", "spineplot")))) { + empty_plot = TRUE + } + + # Draw the individual plot elements... + if (!isTRUE(empty_plot)) { + if (is.null(type_draw)) { + type_draw = switch(type, + "ribbon" = type_ribbon()$draw, + "polygon" = type_polygon()$draw, + "rect" = type_rect()$draw, + "p" = , + "points" = type_points()$draw, + "l" = , + "o" = , + "b" = , + "c" = , + "h" = , + "s" = , + "S" = type_lines(type = type)$draw + ) + } + type_draw( + ibg = ibg, + icol = icol, + ilty = ilty, + ilwd = ilwd, + ipch = ipch, + ix = ix, + ixmax = ixmax, + ixmin = ixmin, + iy = iy, + iymax = iymax, + iymin = iymin, + ilabels = ilabels, + iz = iz, + cex = cex, + dots = dots, + type = type, + x_by = x_by, + iby = ii, + ifacet = i, + facet_by = facet_by, + data_facet = idata, + ngrps = ngrps, + flip = flip, + type_info = type_info, + facet_window_args = facet_window_args + ) + } + } + } - ix = idata[[ii]][["x"]] - iy = idata[[ii]][["y"]] - iz = idata[[ii]][["z"]] - ixmin = idata[[ii]]$xmin - ixmax = idata[[ii]]$xmax - iymin = idata[[ii]]$ymin - iymax = idata[[ii]]$ymax - ilabels = idata[[ii]][["labels"]] - - if (isTRUE(by_continuous)) { - icol = idata[[ii]]$col - ibg = idata[[ii]]$bg + if (!add) { + # save end pars for possible recall later + apar = par(no.readonly = TRUE) + set_saved_par(when = "after", apar) } - # empty plot flag - empty_plot = FALSE - if (isTRUE(empty) || isTRUE(type == "n") || ((length(ix) == 0) && !(type %in% c("histogram", "hist", "rect", "segments", "spineplot")))) { - empty_plot = TRUE - } + }, + list = list( + x = x, + y = y, + xmin = xmin, + xmax = xmax, + ymin = ymin, + ymax = ymax, + by = by, + facet = facet, + facet.args = facet.args, + # data = data, + type = type, + legend = legend, + main = main, + sub = sub, + xlab = xlab, + ylab = ylab, + ann = ann, + xlim = xlim, + ylim = ylim, + axes = axes, + xaxt = xaxt, + yaxt = yaxt, + xaxs = xaxs, + yaxs = yaxs, + xaxb = xaxb, + yaxb = yaxb, + xaxl = xaxl, + yaxl = yaxl, + log = log, + flip = flip, + frame.plot = frame.plot, + grid = grid, + palette = palette, + pch = pch, + lty = lty, + lwd = lwd, + col = col, + bg = bg, + fill = fill, + alpha = alpha, + cex = cex, + add = add, + draw = draw, + empty = empty, + restore.par = restore.par, + file = file, + width = width, + height = height, + asp = asp, + opar = opar, + dots = dots, + x_dep = x_dep, y_dep = y_dep, by_dep = by_dep, facet_dep = facet_dep, + xmin_dep = xmin_dep, xmax_dep = xmax_dep, ymin_dep = ymin_dep, ymax_dep = ymax_dep, + null_by = null_by, x_by = x_by, facet_by = facet_by, null_facet = null_facet, + xlim_user = xlim_user, ylim_user = ylim_user, + type_data = type_data, type_draw = type_draw, + was_area_type = was_area_type, + xlabs = xlabs, ylabs = ylabs, + ygroup = ygroup, + ribbon.alpha = ribbon.alpha, + legend_args = legend_args, + facet_attr = facet_attr, + datapoints = datapoints + ), + env = getNamespace("tinyplot") + ) - # Draw the individual plot elements... - if (!isTRUE(empty_plot)) { - if (is.null(type_draw)) { - type_draw = switch(type, - "ribbon" = type_ribbon()$draw, - "polygon" = type_polygon()$draw, - "rect" = type_rect()$draw, - "p" = , - "points" = type_points()$draw, - "l" = , - "o" = , - "b" = , - "c" = , - "h" = , - "s" = , - "S" = type_lines(type = type)$draw - ) - } - type_draw( - ibg = ibg, - icol = icol, - ilty = ilty, - ilwd = ilwd, - ipch = ipch, - ix = ix, - ixmax = ixmax, - ixmin = ixmin, - iy = iy, - iymax = iymax, - iymin = iymin, - ilabels = ilabels, - iz = iz, - cex = cex, - dots = dots, - type = type, - x_by = x_by, - iby = ii, - ifacet = i, - facet_by = facet_by, - data_facet = idata, - ngrps = ngrps, - flip = flip, - type_info = type_info, - facet_window_args = facet_window_args - ) - } - } - } - - if (!add) { - # save end pars for possible recall later - apar = par(no.readonly = TRUE) - set_saved_par(when = "after", apar) - } } diff --git a/R/tinyplot_add.R b/R/tinyplot_add.R index 069b763a..3980c77e 100644 --- a/R/tinyplot_add.R +++ b/R/tinyplot_add.R @@ -15,21 +15,6 @@ #' mismatches. (An exception is when the original plot arguments---`x`, `y`, #' etc.---are located in the global environment.) #' -#' - There are two important limitations when adding layers to _faceted_ plots: -#' -#' - Avoid resizing the graphics window after the first layer is drawn, since -#' it will lead to any subsequent layers being misaligned. This is a -#' limitation of base R's `graphics` engine and cannot be reliably preempted -#' or corrected by `tinyplot`. Note that resizing non-faceted plots is -#' always fine, though. See: -#' -#' -#' - On Positron, specifically, alignment issues may occur even without -#' resizing. A warning will be triggered when (i) Positron is detected and -#' (ii) a user attempts to add layers to a faceted plot. Again, this issue -#' is not present for non-faceted plots. See the upstream bug report: -#' -#' #' - Automatic legends for the added elements will be turned off. #' #' @param ... All named arguments override arguments from the previous calls. diff --git a/inst/tinytest/test-restore_par.R b/inst/tinytest/test-restore_par.R index de529d59..64e322d2 100644 --- a/inst/tinytest/test-restore_par.R +++ b/inst/tinytest/test-restore_par.R @@ -23,7 +23,7 @@ f2 = function() { tinyplot( mpg ~ wt | cyl, mtcars, pch = 19, - grid = grid(), + grid = TRUE, legend = legend("right!", title = "How many cylnders do you have?") ) lines(lowess(mtcars[["wt"]], mtcars[["mpg"]])) @@ -38,11 +38,11 @@ f3 = function() { tinyplot( mpg ~ wt | cyl, mtcars, pch = 19, - grid = grid(), + grid = TRUE, legend = legend("right!", title = "How many cylnders do you have?"), restore.par = TRUE ) lines(lowess(mtcars[["wt"]], mtcars[["mpg"]])) plot(1:10) } -expect_snapshot_plot(f3, label = "restore_par_TRUE") +# expect_snapshot_plot(f3, label = "restore_par_TRUE") diff --git a/man/tinyplot_add.Rd b/man/tinyplot_add.Rd index 791b737a..4b3a510a 100644 --- a/man/tinyplot_add.Rd +++ b/man/tinyplot_add.Rd @@ -33,20 +33,6 @@ We cannot guarantee correct behavior if the original plot was created with the atomic \code{\link{tinyplot.default}} method, due to potential environment mismatches. (An exception is when the original plot arguments---\code{x}, \code{y}, etc.---are located in the global environment.) -\item There are two important limitations when adding layers to \emph{faceted} plots: -\itemize{ -\item Avoid resizing the graphics window after the first layer is drawn, since -it will lead to any subsequent layers being misaligned. This is a -limitation of base R's \code{graphics} engine and cannot be reliably preempted -or corrected by \code{tinyplot}. Note that resizing non-faceted plots is -always fine, though. See: -\url{https://github.com/grantmcdermott/tinyplot/issues/313} -\item On Positron, specifically, alignment issues may occur even without -resizing. A warning will be triggered when (i) Positron is detected and -(ii) a user attempts to add layers to a faceted plot. Again, this issue -is not present for non-faceted plots. See the upstream bug report: -\url{https://github.com/posit-dev/positron/issues/7316} -} \item Automatic legends for the added elements will be turned off. } }