In base R (and in sp
), I wish to create arrows with a predefined shape but flexible rotation centered at provided coordinates. I came up with the following function:
my_arrow <- function(x,y, rotate=0, col=par("fg"), cex=1) {
xbase <- c(1.2,0.2,0.2,-1.2, -1.2, 0.2, 0.2)
ybase <- c(0,1,0.5,0.5,-0.5,-0.5,-1)
rotM <- matrix(c(cos(rotate*pi/180), sin(rotate*pi/180), -sin(rotate*pi/180), cos(rotate*pi/180)), nrow=2)
transf <- function(x1,y1) cex * rotM %*% c(x1,y1) + c(x,y)
ans <- t(mapply(transf, xbase, ybase))
polygon(x=ans[,1], y=ans[,2], col=col)
}
This produces the arrow I want if rotation=0
, however it gets distorted when I do rotate. For instance,
plot(1:2, type="p", col="white", xlim=c(-5,5), ylim=c(-10,10))
my_arrow(0,0, rotate=45)
produces the chart below.
I think I need to apply some special types of coordinates, but I am stuck. Any ideas?
(The arrows
function will not work for me since I have another shape in mind. Using gridBase
and some rotated viewports sounds like overkill to me.)
After inspecting the function shapes::rotatexy
, I found the solution myself: I need to address the aspect ratio issue. In the end, I came up with the following function which works fine for me:
my_arrow <- function(x,y, rotate=0, col=par("fg"), border=par("fg"), cex=1) {
scale_base <- strwidth("O")/2.4
xbase <- c(1.2,0.2,0.2,-1.2, -1.2, 0.2, 0.2) * scale_base
ybase <- c(0,1,0.5,0.5,-0.5,-0.5,-1) * scale_base
rotM <- matrix(c(cos(rotate*pi/180), sin(rotate*pi/180), -sin(rotate*pi/180), cos(rotate*pi/180)), nrow=2)
transf <- function(x1,y1) cex * rotM %*% c(x1,y1) + c(x,y)
ans <- t(mapply(transf, xbase, ybase))
# this is deliberately taken from shapes::rotatexy
user <- par("usr")
pin <- par("pin")
sy <- user[4] - user[3]
sx <- user[2] - user[1]
ans[,2] <- y + (ans[,2]-y) * sy/sx * pin[1]/pin[2]
polygon(x=ans[,1], y=ans[,2], col=col, border=border)
}
So, when I call:
plot(1:2, type="p", col="white", xlim=c(-5,5), ylim=c(-10,10))
my_arrow(0,0, rotate=45, cex=5)
I get what I wanted: