Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is an inch? Setting the length for arrows

Tags:

plot

r

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)

A barplot with three bars with heights increasing from left to right; each has an upward-pointing arrow starting within the bar and extending above it.

And here it is on another:

png('test.png', width = 8, height = 8)

The same plot, but scaled larger; the arrows look relatively smaller on this version despite the same code (besides the call to png()) underlying it.

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?

like image 906
MichaelChirico Avatar asked Oct 31 '17 12:10

MichaelChirico


1 Answers

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):

enter image description here

And (8x8):

enter image description here

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.

like image 61
MichaelChirico Avatar answered Sep 24 '22 03:09

MichaelChirico