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
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ related to plot layering. See "Bug fixes" below.

### Bug fixes

- Fixed a bug where consecutive plots with (i) logged axes under (ii) a dynamic
theme would error, due to a stale `par("xlog")`/`par("ylog")` state. We now
avoid this by grabbing the log state directly from the top-level `log`
argument instead. Thanks to @eddelbuettel for the report.
(#725 @grantmcdermott)
- `type_barplot()` receives several consistency improvements and bug fixes:
- Passing a named atomic vector now uses the names as the bar categories,
matching base `barplot()`. (#714 @grantmcdermott)
Expand Down
15 changes: 11 additions & 4 deletions R/facet.R
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ draw_facet_window = function(
}


# Both dynmar branches below measure tick labels before the plot.window()
# calls in the facet loop, so the log state has to come from the caller's
# `log` argument -- par("xlog")/par("ylog") still describe the previous plot
# at that point (#725).
.xlog = grepl("x", log, fixed = TRUE)
.ylog = grepl("y", log, fixed = TRUE)

# if breaks are provided use these (but only if x/ylabs are null)
if (!is.null(xaxb) && !is.null(xlabs)) xlabs = xaxb
if (!is.null(yaxb) && !is.null(ylabs)) ylabs = yaxb
Expand Down Expand Up @@ -196,7 +203,7 @@ draw_facet_window = function(
}
yaxlabs = axis_tick_labels(
.ylabset,
lim = ylim, axb = yaxb, axl = yaxl, log = par("ylog"),
lim = ylim, axb = yaxb, axl = yaxl, log = .ylog,
free_lims = .yfree, cex = .cex_yaxs
)
whtsbp = if (!is.null(yaxr)) {
Expand Down Expand Up @@ -229,7 +236,7 @@ draw_facet_window = function(
}
xaxlabs = axis_tick_labels(
.xlabset,
lim = xlim, axb = xaxb, axl = xaxl, log = par("xlog"),
lim = xlim, axb = xaxb, axl = xaxl, log = .xlog,
free_lims = .xfree, cex = .cex_xaxs
)
whtsbp = if (!is.null(xaxr)) {
Expand Down Expand Up @@ -309,7 +316,7 @@ draw_facet_window = function(
# extra whitespace bump on the y axis
yaxlabs = axis_tick_labels(
y_axis_labels(type, y, ylabs, xlabs, flip),
lim = ylim, axb = yaxb, axl = yaxl, log = par("ylog"),
lim = ylim, axb = yaxb, axl = yaxl, log = .ylog,
cex = .cex_yaxs
)
if (!is.null(yaxr)) {
Expand All @@ -323,7 +330,7 @@ draw_facet_window = function(
# extra whitespace bump on the x axis
xaxlabs = axis_tick_labels(
x_axis_labels(xlabs),
lim = xlim, axb = xaxb, axl = xaxl, log = par("xlog"),
lim = xlim, axb = xaxb, axl = xaxl, log = .xlog,
cex = .cex_xaxs
)
if (!is.null(xaxr)) {
Expand Down
49 changes: 37 additions & 12 deletions R/tinyAxis.R
Original file line number Diff line number Diff line change
Expand Up @@ -164,34 +164,59 @@ x_axis_labels = function(xlabs) {
## clear every panel's ticks, so all of them are measured and the widest wins.
axis_tick_labels = function(labelset, lim, axb = NULL, axl = NULL, log = FALSE,
free_lims = NULL, cex = 1) {
ticks = function(l) {
u = axis_usr(l, log = log, axb = axb)
axisTicks(usr = u[["usr"]], log = u[["log"]])
}
if (!is.null(labelset)) {
out = labelset[[1L]]
} else if (!is.null(free_lims)) {
sets = lapply(free_lims, function(l) {
axisTicks(usr = extendrange(l, f = 0.04), log = log)
})
sets = lapply(free_lims, ticks)
widths = vapply(
sets,
function(s) max(strwidth(s, "inches", cex = cex)),
numeric(1L)
)
out = sets[[which.max(widths)]]
} else {
# A single distinct value gives a zero-width range that extendrange() can't
# pad and axisTicks() can't tick, so widen it the way plot.window() does.
# An explicit `at` (xaxb/yaxb) supplies its own ticks, hence the guard.
usr = if (diff(lim) == 0 && is.null(axb)) {
lim + c(-0.5, 0.5)
} else {
extendrange(lim, f = 0.04)
}
out = axisTicks(usr = usr, log = log)
out = ticks(lim)
}
if (!is.null(axl)) out = tinylabel(out, axl)
out
}


## Axis limits in the coordinate space axisTicks() and par("usr") speak.
##
## Callers hold limits in *data* units, but on a log axis axisTicks() reads
## its `usr` in log10 units. Keeping that conversion here -- rather than
## leaving each call site to remember it -- is the point of this helper: the
## units contract and the padding rules stay in one place. Returns the padded
## limits together with the log flag they were built under, so the caller
## hands axisTicks() a matching pair.
##
## Margin measurement runs *before* plot.window(), so `log` must come from the
## caller's own `log=` argument. par("xlog")/par("ylog") still describe the
## previous plot on the device at that point (#725).
##
## A log axis can't represent a zero or negative limit, so those fall back to a
## linear measurement: plot.window() raises its own, clearer complaint moments
## later, and log10() here would only put "NaNs produced" in front of it.
axis_usr = function(lim, log = FALSE, axb = NULL) {
log = isTRUE(log) && all(is.finite(lim)) && all(lim > 0)
if (log) lim = log10(lim)
# A single distinct value gives a zero-width range that extendrange() can't
# pad and axisTicks() can't tick, so widen it the way plot.window() does.
# An explicit `at` (xaxb/yaxb) supplies its own ticks, hence the guard.
usr = if (diff(lim) == 0 && is.null(axb)) {
lim + c(-0.5, 0.5)
} else {
extendrange(lim, f = 0.04)
}
list(usr = usr, log = log)
}


## Margin lines a side must reserve to clear its tick labels ("whtsbp").
##
## Under las 1:2 the y labels are horizontal and their *width* eats into mar[2];
Expand Down
25 changes: 19 additions & 6 deletions R/tinyplot.R
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,11 @@ tinyplot.default = function(
.whtsbp_y_raw = 0
.whtsbp_x_raw = 0
.las = get_tpar("las", tpar_list = .tpars, default = par("las"))
# Same reasoning for the log state: par("xlog")/par("ylog") still describe
# the previous plot on this device until plot.window() runs, which is after
# this block. Read the user's `log` argument instead (#725).
.xlog = grepl("x", log, fixed = TRUE)
.ylog = grepl("y", log, fixed = TRUE)
# A flipped boxplot draws the x variable up the side and the y variable
# along the bottom, so a rotation has to be measured against the side its
# labels actually land on. (The las branches below keep indexing 1/2
Expand All @@ -1328,7 +1333,7 @@ tinyplot.default = function(
if (!is.null(yaxr) || .las %in% 1:2) {
yaxlabs = axis_tick_labels(
y_axis_labels(type, y, ylabs, xlabs, flip),
lim = ylim, axb = yaxb, axl = yaxl, log = par("ylog"),
lim = ylim, axb = yaxb, axl = yaxl, log = .ylog,
cex = .cex_yaxs
)
if (!is.null(yaxr)) {
Expand All @@ -1343,7 +1348,7 @@ tinyplot.default = function(
if (!is.null(xaxr) || .las %in% 2:3) {
xaxlabs = axis_tick_labels(
x_axis_labels(xlabs),
lim = xlim, axb = xaxb, axl = xaxl, log = par("xlog"),
lim = xlim, axb = xaxb, axl = xaxl, log = .xlog,
cex = .cex_xaxs
)
if (!is.null(xaxr)) {
Expand Down Expand Up @@ -1387,17 +1392,25 @@ tinyplot.default = function(
max(0, fin / par("csi") - pad)
}
if (!is.null(xaxr)) {
.u = axis_usr(xlim, log = .xlog, axb = xaxb)
.at = if (!is.null(xlabs)) as.numeric(xlabs) else
axisTicks(usr = extendrange(xlim, f = 0.04), log = par("xlog"))
.ins = axis_tick_inset(.at, extendrange(xlim, f = 0.04), .span("x"))
axisTicks(usr = .u[["usr"]], log = .u[["log"]])
# axisTicks() reports tick locations in data units, but the inset is a
# visual fraction of the panel, so both sides of it live in usr space.
if (.u[["log"]]) .at = log10(.at)
.ins = axis_tick_inset(.at, .u[["usr"]], .span("x"))
.ovh = tick_label_overhang(xaxlabs, cex = .cex_xaxs, srt = xaxr,
side = .xside, inset = .ins)
.dyn = .add_lean(.dyn, .ovh, .flank(.xside))
}
if (!is.null(yaxr)) {
.u = axis_usr(ylim, log = .ylog, axb = yaxb)
.at = if (!is.null(ylabs)) as.numeric(ylabs) else
axisTicks(usr = extendrange(ylim, f = 0.04), log = par("ylog"))
.ins = axis_tick_inset(.at, extendrange(ylim, f = 0.04), .span("y"))
axisTicks(usr = .u[["usr"]], log = .u[["log"]])
# axisTicks() reports tick locations in data units, but the inset is a
# visual fraction of the panel, so both sides of it live in usr space.
if (.u[["log"]]) .at = log10(.at)
.ins = axis_tick_inset(.at, .u[["usr"]], .span("y"))
.ovh = tick_label_overhang(yaxlabs, cex = .cex_yaxs, srt = yaxr,
side = .yside, inset = .ins)
.dyn = .add_lean(.dyn, .ovh, .flank(.yside))
Expand Down
10 changes: 10 additions & 0 deletions inst/tinytest/test-tinyAxis.R
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,13 @@ f = function() {
xaxl = cyl_labs, xaxr = 45, theme = "clean")
}
expect_snapshot_plot(f, label = "axis_rotation_flip")


# Dynmar with repeated log calls (#725)
f = function() {
tinytheme("ipsum")
on.exit(tinytheme())
plt(disp ~ mpg, data = mtcars, log = "y")
plt(disp ~ mpg, data = mtcars, log = "y")
}
expect_silent(f())