Search code examples
rgraphicscoordinate-systems

Rotated Arrow in Base R


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

enter image description here


Solution

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

    enter image description here