Skip to content
Open
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 @@ -153,10 +153,13 @@ Theme fixes:
and drawing them as standalone bars. This is useful for Likert plots, where
you want to show a neutral categories (e.g., "Unsure") apart from the
diverging stack. Thanks to @strengejacke for the suggestion.
- `type_text()` (and `type = "text"`) gains a `labeller` argument that is passed
to `tinylabel()` for formatting the text labels. This is useful for ensuring
that text annotations match a formatted axis, e.g. `labeller = "%"` to display
the labels as percentages. (#620 @grantmcdermott)
- `type_text()` gains two new arguments:
- a `labeller` argument that is passed to `tinylabel()` for formatting the
text labels. (#620 @grantmcdermott)
- a `repel` argument that automatically nudges overlapping text labels apart.
One limitation is that the repulsion logic operates with groups. So there
may still be some overlapping text for for grouped data.
(#621 @grantmcdermott)

### Bug fixes

Expand Down
60 changes: 52 additions & 8 deletions R/type_text.R
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@
#' @param xpd Logical value or `NA` denoting text clipping behaviour, following
#' \code{\link[graphics]{par}}.
#' @param srt Numeric giving the desired string rotation in degrees.
#' @param repel Logical or numeric controlling automatic repulsion of
#' overlapping text labels. The default `FALSE` draws the labels at their
#' exact (`x`,`y`) coordinates. `TRUE` nudges overlapping labels apart using a
#' force-directed algorithm (labels are pushed off each other and then sprung
#' back toward their original positions). A numeric value does the same but
#' additionally enforces that much minimum padding (in user coordinates)
#' between labels. Works best with the default centered text placement (i.e.
#' without `pos`). **Caveat:** The repulsion logic currently operates
#' within each group rather than across groups. So the text of different
#' groups may still overlap. See Examples.
#' @param clim Numeric giving the lower and upper limits of the character
#' expansion (`cex`) normalization for bubble charts.
#' @inheritParams graphics::text
Expand All @@ -30,10 +40,19 @@
#'
#' # pass explicit `labels` arg if you want specific text
#' tinyplot(1:12, type = "text", labels = month.abb)
#'
#' # you can also use a labeller function (passed to `tinylabel`) to
#' # customize
#' tinyplot(1:12, type = "text", labels = month.abb, labeller = toupper)
#'
#' # for advanced customization, it's safer to pass args through `type_text()`
#' tinyplot(1:12, type = type_text(
#' labels = month.abb, family = "HersheyScript", srt = -20))
#' tinyplot(
#' 1:12,
#' type = type_text(
#' labels = month.abb,
#' family = "HersheyScript",
#' srt = -20)
#' )
#'
#' # same principles apply to grouped and/or facet data
#' tinyplot(mpg ~ hp | factor(cyl),
Expand All @@ -58,10 +77,18 @@
#' )
#' )
#'
#' # use `labeller` to format the labels, e.g. to match a formatted axis
#' d = data.frame(x = c("A", "B"), y = c(0.5, 0.8))
#' tinyplot(y ~ x, data = d, type = "bar", ylim = c(0, 1), yaxl = "%")
#' tinyplot_add(type = type_text(labeller = "%", adj = c(0.5, -0.5)))
#' # use `repel = TRUE` to automically nudge overlapping labels apart
#' tinyplot(
#' mpg ~ wt, data = mtcars,
#' type = type_text(labels = row.names(mtcars), repel = TRUE)
#' )
#'
#' # limitation: `repel` logic currently works per group, so grouped text data
#' # may still overlap
#' tinyplot(
#' mpg ~ wt | factor(cyl), data = mtcars,
#' type = type_text(labels = row.names(mtcars), repel = TRUE)
#' )
#'
#' @export
type_text = function(
Expand All @@ -75,6 +102,7 @@ type_text = function(
vfont = NULL,
xpd = NULL,
srt = 0,
repel = FALSE,
clim = c(0.5, 2.5)
) {
out = list(
Expand All @@ -86,7 +114,8 @@ type_text = function(
family = family,
font = font,
xpd = xpd,
srt = srt
srt = srt,
repel = repel
),
data = data_text(labels = labels, labeller = labeller, clim = clim),
name = "text"
Expand Down Expand Up @@ -133,7 +162,8 @@ draw_text = function(
family = NULL,
font = NULL,
xpd = NULL,
srt = 0
srt = 0,
repel = FALSE
) {
if (is.null(xpd)) {
xpd = par("xpd")
Expand All @@ -142,6 +172,20 @@ draw_text = function(
vfont = NULL
}
fun = function(ix, iy, ilabels, icol, icex, ...) {
# Optionally repel overlapping labels. Measurement requires user
# coordinates, which are only correct here (post `plot.window()`), not in
# the type's data function. See `repel_text()` in R/repel.R.
if (!isFALSE(repel) && length(ix) > 1) {
min_gap = if (isTRUE(repel)) 0 else as.numeric(repel)
w = strwidth(ilabels, units = "user", cex = icex, font = font, family = family)
h = strheight(ilabels, units = "user", cex = icex, font = font, family = family)
rp = repel_text(
x = ix, y = iy, widths = w, heights = h,
min_gap = min_gap, axis = "both"
)
ix = rp[["x"]]
iy = rp[["y"]]
}
text(
x = ix,
y = iy,
Expand Down
94 changes: 94 additions & 0 deletions inst/tinytest/_tinysnapshot/text_repel.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions inst/tinytest/test-type_text.R
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,12 @@ f = function() {
tinyplot_add(type = type_text(labeller = "%", adj = c(0.5, -0.5)))
}
expect_snapshot_plot(f, label = "text_labeller_percent")


# repel arg nudges overlapping labels apart (#318)
f = function() {
tinyplot(mpg ~ wt, data = mtcars,
type = type_text(labels = row.names(mtcars), repel = TRUE),
main = "repel = TRUE")
}
expect_snapshot_plot(f, label = "text_repel")
41 changes: 35 additions & 6 deletions man/type_text.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading