Skip to content

Instantly share code, notes, and snippets.

@k-barton
Last active June 27, 2023 01:04
Show Gist options
  • Save k-barton/ca144a569a70ca3a6d7ddcacfefbb4d3 to your computer and use it in GitHub Desktop.
Save k-barton/ca144a569a70ca3a6d7ddcacfefbb4d3 to your computer and use it in GitHub Desktop.
Draw a rectangle surrounding text in a plot
# All arguments as in `graphics::text`, except pad which specifies padding (optionally
# for vertical and horizontal) around the text as a fraction of a character width.
# `...` is passed to `rect`
textbox <-
function(x, y, labels, adj = NULL,
pos = NULL,
offset = 0.5,
pad = c(0, 0),
vfont = NULL,
cex = par("cex"),
font = NULL, ...) {
xy <- xy.coords(x, y, recycle = TRUE, setLab = FALSE)
n <- length(xy$x)
if(is.call(labels)) labels <- as.expression(labels)
pad <- as.numeric(rep(pad, length.out = 2L))
if(is.null(pos))
pos <- NA
if(is.null(font))
font <- par("font")
u2ix <- function(x) grconvertX(x, from = "user", to = "inch")
u2iy <- function(x) grconvertY(x, from = "user", to = "inch")
i2ux <- function(x) grconvertX(x, from = "in", to = "user")
i2uy <- function(x) grconvertY(x, from = "in", to = "user")
# from now on all is in inches
x <- u2ix(xy$x)
y <- u2iy(xy$y)
offset <- grconvertX(offset, from = "chars", to = "in")
pad <- grconvertX(pad, from = "chars", to = "in")
adj <- if (is.null(adj)) {
c(.5, .5)
} else if (is.numeric(adj)) {
if (length(adj) == 0) {
c(.5, .5)
} else if (length(adj) == 1L) {
c(adj, .5)
} else {
adj[1L:2L]
}
} else stop("invalid 'adj' value")
# values taken from R source src/library/grDevices/src/dev*.c
yCharOffset <- switch(names(dev.cur()),
windows = 0.40,
"xfig" = , "pdf" = , "postscript" = 0.3333,
pictex = 0,
#CAIRO
"png" =, "jpeg"=, "svg"=, "png"=,
"cairo_pdf"=, "cairo_ps"=, "tiff"=, "bmp"= 0.3333,
"null device" = stop("no device is open"),
stop("unknown device")
)
z <- .mapply(function(x, y, s, pos, cex, font, pad, ...) {
switch(pos, { # 1 bottom
y <- y - offset
adj <- c(0.5, 1 - (0.5 - yCharOffset))
}, { # 2 left
x <- x - offset
adj <- c(1, yCharOffset)
}, { # 3 top
y <- y + offset
adjx <- 0.5
adjy <- 0
adj <- c(.5, 0)
}, { # 4 right
x <- x + offset
adj <- c(0, yCharOffset)
}, { })
w <- strwidth(s, cex = cex, units = "in", font = font, vfont = vfont)
h <- strheight(s, cex = cex, units = "in", font = font, vfont = vfont)
x <- x - (adj[1L] * w)
y <- y - (adj[2L] * h)
c(x - pad[1L], y - pad[2L], x + w + pad[1L], y + h + pad[2L])
},
list(x = x, y = y, s = labels, pos = pos, cex = cex,
font = font, ...),
MoreArgs = list(pad = pad))
z <- do.call("rbind", z)
rect(i2ux(z[, 1L]), i2uy(z[, 2L]),
i2ux(z[, 3L]), i2uy(z[, 4L]),
...)
}
@k-barton
Copy link
Author

lab <- expression(
 {f * minute}(x),
 hat(beta) == (X^t * X)^{-1} * X^t * y,
 bar(x) == sum(frac(x[i], n), i==1, n),
 paste(frac(1, sigma*sqrt(2*pi)), " ",
         plain(e)^{frac(-(x-mu)^2, 2*sigma^2)}))

offset <- 3
x <- 0
y <- 0
plot(0, 0, xlim = c(-8, 8), ylim = c(-2, 2))
textbox(x, y, lab, cex =  1:4, pos = 1:4, offset = offset,
            pad = .4,
            col = "#0080ff99", border = NA,
            xpd = NA)
# recycling behaviour is different between text and textbox
text(x, y, rep(lab, 4), cex = 1:4, pos = 1:4, offset = offset,
     xpd = NA)

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment