Somewhat inexplicably, the length
parameter in arrows
is specified in inches (from ?arrows
):
length
length of the edges of the arrow head (in inches).
R source even goes so far as to explicitly make note that this measurement is in inches in a comment, highlighting how peculiar this design is.
That means the relative size of the arrows depends on dev.size()
. What's not clear is how to translate inches into axis units (which are infinitely more useful in the first place). Here's a simplified version:
h = c(1, 2, 3)
xs = barplot(h, space = 0, ylim = c(0, 4))
arrows(xs, h - .5, xs, h + .5,
length = .5*mean(diff(xs)))
How this displays will depend on the device. E.g. here is the output on this device:
png('test.png', width = 5, height = 5)
And here it is on another:
png('test.png', width = 8, height = 8)
It's a bit of an optical illusion to tell on sight, but the arrows are indeed the same width in the two plots. How can I control this so that both plots (which convey the same data) display identically? More specifically, how can I make sure that the arrows are exactly .5 plot units in width?
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 formc(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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With