I spent far too much time in the rabbit hole on this, but here goes. I'll document a bit of my journey first to aid others who happen upon this in the types of nooks and crannies to search when trying to pull yourself up by your bootstraps.
I started looking in the source of arrows
, but to no avail, since it quickly dives into internal code. So I searched the R source for "C_arrows"
to find what's happening; luckily, it's not too esoteric, as far as R internal code goes. Poking around it seems the workhorse is actually GArrow
, but this was a dead end, as it seems the length
parameter isn't really transformed there (IIUC this means the conversion to inches is done for the other coordinates and length
is untouched). But I happened to notice some GConvert
calls that looked closer to what I want and hoped to find some user-facing function that appeals to these directly.
This led me to go back to R and to simply run through the gamut of functions in the same package as arrows
looking for anything that could be useful:
ls(envir = as.environment('package:grDevices'))
ls(envir = as.environment('package:graphics'))
Finally I found three functions in graphics
: xinch
, yinch
, and xyinch
(all found on ?xinch
) are used for the opposite of my goal here -- namely, they take inches and convert them into device units (in the x, y, and x&y directions, respectively). Luckily enough, these functions are all very simple, e.g. the work horse of xinch
is the conversion factor:
diff(par("usr")[1:2])/par("pin")[1L]
Examining ?par
(for the 1,000,000th time), indeed pin
and usr
are exactly the graphical parameter we need (pin
is new to me, usr
comes up here and there):
pin
The current plot dimensions, (width, height), in inches.
usr
A vector of the form c(x1, x2, y1, y2)
giving the extremes of the user coordinates of the plotting region.
Hence, we can convert from plot units to inches by inverting this function:
xinch_inv = function(dev_unit) {
dev_unit * par("pin")[1L]/diff(par("usr")[1:2])
}
h = c(1, 2, 3)
xs = barplot(h, space = 0, ylim = c(0, 4))
arrows(xs, h - .5, xs, h + .5,
# just convert plot units to inches
length = xinch_inv(.5*mean(diff(xs))))
Resulting in (5x5):

And (8x8):

One further note, it appears length
is the length of each side of the arrow head -- using length = xinch_inv(.5), code = 3, angle = 90
results in segments as wide as the bars (i.e., 1).
On the off chance you're interested, I've packaged these in my package as xdev2in
, etc.; GitHub only for now.