0

I am looking for a way to add an arrow to a plot in base R, such that the arrow will be filled with a grey-gradient color, like this:

enter image description here

I have seen this solution, but that seems quite complex and not that flexible: I need to draw a great mahy arrows, in a great many plots, all potentially with different length and width. I am aware of the shape package, but that seems to only be able to fill arrowheads, and does not provide a fillable "base" of the arrow. Any suggestions?

Community
  • 1
  • 1
Steve G. Jones
  • 325
  • 2
  • 10
  • Base R doesn't really deal with gradients, You have to fake it by drawing a bunch of little rectangles with different colors. You basically have a very simple polygon shape you want to create. If you are OK with having the gradient only in the tail of the arrow, things are even easier. But you need to be much more specific on what you want the requirements to me. How do you want to be able to specific, width, length, head size, gradient color values, direction, etc. This question is lacking in specifics to be answerable in it's current state. – MrFlick Sep 14 '14 at 22:17
  • To emphasize @MrFlick's comments, most of R-Core thinks this would be an effort to emulate the notorious chart-junk techniques foisted on the world by Excel and PowerPoint. Real statisticians let the data speak for itself. – IRTFM Sep 14 '14 at 23:00
  • @BondedDust Gradient colours *can* convey meaningful data, which is why they are implemented in `ggplot`. I suspect that R-Core's decision to not implement gradients was a matter of priority, rather than ideology. – nograpes Sep 14 '14 at 23:17
  • I agree with @nograpes, gradient-filling can indeed be meaningful. Although this may be infrequent, OP's question is valid and if he has the need for such symbols, why not help him out? Anyway, polygons can be filled with gradients, and I can understand people wanting to fill arrow shaped symbols that way as well. – Peter Verbeet Sep 15 '14 at 18:32

2 Answers2

1

Here is one way to get you started, along the lines of @MrFlick's suggestion. You probably want to encapsulate this inside a function that will allow you to exert more influence over the size of the arrowhead, the width of the base and arrow head, the smoothness of the gradient, etc.

#empty box
plot(c(-1, 2), c(-1, 10), ,type="n",axes=FALSE, xlab = "", ylab = "")
# plot the arrow, without a fill
polygon(c(0,0,-.25,.5,1.25,1,1,0), y = c(0,6,6, 8,6,6,0,0), border = NA)
# create gradient colors
nslices = 100
cols <- colorRampPalette(colors = c("white", "black"))(nslices)
# split the base of the arrow in nslices and fill each progressively
ys <- seq(0,6, len = nslices + 1)
for (i in 1:nslices) {
  polygon(c(0,0,1,1), c(ys[i], ys[i+1], ys[i+1], ys[i]), col = cols[i], border = NA)
}
# add a filled arrowhead
polygon(c(-.25, .5, 1.25, -.25), c(6, 8, 6, 6), col = "black")

This would get you an arrow like this:

enter image description here

HTH, Peter

Peter Verbeet
  • 1,786
  • 2
  • 13
  • 29
1

using the arrow defined in the linked question, now in base graphics

# create a black arrow, saved as external file
library(grid)
png("mask.png")
grid.polygon(c(-0.06, 0.06, 0.06, 0.15, 0, -0.15, -0.06),
             c(-5, -5, 2.5, 2, 5, 2, 2.5), gp=gpar(fill="black"),
             def="native",
             vp=viewport(xs=c(-0.15, 0.15), ys=c(-5, 5)))
dev.off()

## read back in as colour matrix
library(png)
m <- readPNG("mask.png", native=FALSE)
mask <- matrix(rgb(m[,,1],m[,,2],m[,,3]),
               nrow=nrow(m))

rmat <- matrix(grey(seq(0,1,length=nrow(m))),
               nrow=nrow(m), ncol=ncol(m))
rmat[mask == "#FFFFFF"] <- NA


## use in base plot
set.seed(12321)
plot(1:10, rnorm(10))
rasterImage(rmat, 2, -1, 2.5, 0)

enter image description here

Edit:

you don't have to use a temporary file to create the mask, it's just (much more) convenient than fiddling with logical matrices. Here's a starting point to create the arrow directly as a matrix,

marrow <- function(nr=500, nc=300, col = grey(seq(0, 1, length=nr))){

  skin <- matrix(col, nrow=nr, ncol=nc)
  head <- lower.tri(matrix(TRUE, nrow=nc/2, ncol=nc/2))
  skull <- cbind(head[seq(nc/2,1),], head[seq(nc/2,1),seq(nc/2,1)])

  rib <- matrix(TRUE, nrow=nr-nrow(skull), ncol=nc/4)
  trunk <- cbind(rib, !rib, !rib, rib)
  skeleton <- rbind(skull, trunk)
  skin[skeleton] <-  NA_character_
  skin
}

grid.newpage()
grid.raster(marrow(), 
            width = unit(1,"npc"), 
            height=unit(1,"npc"))

enter image description here

baptiste
  • 75,767
  • 19
  • 198
  • 294
  • Hi Baptiste, this is intriguing. I had seen the approach before, and like the flexibility it allows. I am a little hesitant to having to save the png, since that is a bit tricky when my colleagues use the function as well. I would prefer to circumvent the saving and reading of intermediate files. Is it possible to simply store the info in an object, without saving it as a png to a file? – Steve G. Jones Sep 16 '14 at 14:09
  • sure, you can save the matrix `m` or `mask` or even `rmat` once and for all, but you won't be able to change the resolution and general shape (aside from stretching). – baptiste Sep 16 '14 at 14:14
  • will stretching affect the resolution/sharpness when I print the resulting figure? I need a bunch of arrows, varying in width and length and need all of them to be print quality. Thanks. – Steve G. Jones Sep 16 '14 at 17:15
  • hard to say, it depends on the resolution of the original png file and how much you'll stretch it. – baptiste Sep 16 '14 at 18:35