Search code examples
rgridcircular-dependency

R Grid package solution for plotting angles on a circle


I would like to draw angles on the perimeter of a circle. This solution with grid package looked promising Circular plot with vectors in R for the customisation I need but I'm having trouble plotting my angles on the edge of the circle.

This is close to what I need with circular package. It has arrows that intersect the circle edge at specific angles and more angles plotted as points on the perimeter

angles <- read.table(text ='
degree Year
    120  2000
     30  2001
  160  2002
    35 2003
   150  2004    
   90  2005
    70  2006
      20  2007',header=T)
angles = circular(angles$degree, units = "degrees", 
                  template = "geographics") 
plot(angles, col = "black",stack=F) 
arrows.circular(limits, col = c("red","red","blue"))

Plot with circular package

However, I'm having trouble with using grid package for segments and points. I thought a solution might be to convert the angles to cartesian coordinates using a function here <https://gis.stackexchange.com/questions/465134/create-conical-gradient-around-a-point-using-r< but that doesn't give me the result I need.

a=57
b=75
c=42
limits<-data.frame(lims=c(a,b,c))

angles <- read.table(text ='
degree Year
    120  2000
     30  2001
  160  2002
    35 2003
   150  2004    
   90  2005
    70  2006
      20  2007',header=T)

limits.custom <- function(limits){
  pushViewport(viewport(layout.pos.col=2,layout.pos.row=2))
  apply(limits,1,function(x){
    pushViewport(viewport(angle=x['lims']))  
    grid.segments(x0=0.5,y0=0.5,x1=0.8,y1=0.5,gp=gpar(lty=5,lwd=2))
    popViewport()
  })
  popViewport()
}

circ_coords <- function(r, t, h, k){
  t <- t * pi/180
  x <- r * cos(t) + h
  y <- r * sin(t) + k
  z <- c(x, y)
  names(z) <- c('X', 'Y')
  return(z)
}

angles.custom <- function(angles){
  pushViewport(viewport(layout.pos.col=2,layout.pos.row=2))
  apply(angles,1,function(x){
    coord<-circ_coords(r=0.5,t=x['degree'],h=0.5,k=0.5)
    grid.points(x=coord["X"],y=coord["Y"],gp=gpar(pch=16))
  })
  popViewport()
}

pushViewport(viewport(layout.pos.col=2,layout.pos.row=2))
grid.circle(x=0.5,y=0.5,r=0.5,gp = gpar(ltw=c(3),col=c('black')))
limits.custom(limits)
angles.custom(angles)
pushViewport(viewport(layout.pos.col=2,layout.pos.row=2))
grid.segments(x0=0.5,y0=0,x1=0.5,y=1,gp=gpar(col='grey'))
grid.segments(x0=0,y0=0.5,x1=1,y=0.5,gp=gpar(col='grey'))
popViewport()

Solution

  • Here's a quick hack using ggplot2.

    I'm choosing to change limits in two ways:

    • it's typically more convenient to have your "x" and "y" variables as the same name in all of the data sources, so I'm changing limits$lims to limits$degree to match angles;
    • I'm adding a nominal variable type in order to demonstrate ggplot's categorical way of dealing with things. While it's certainly possible to include literal colors instead, it's a good thing (especially if you want legends) to allow ggplot to use the "real category names" that you have, and then if desired manually control the colors assigned to each level in the categoricals
    library(ggplot2)
    angles <- read.table(text ='
    degree Year
        120  2000
         30  2001
      160  2002
        35 2003
       150  2004    
       90  2005
        70  2006
          20  2007',header=T)
    a=57
    b=75
    c=42
    limits <- data.frame(degree=c(a,b,c), type=c("R","R","B"))
    ggplot(angles, aes(x=degree)) +
      geom_point(y = 1) + 
      geom_segment(
        y = 0, yend = 1,
        aes(xend = degree, color = type), 
        arrow = grid::arrow(),
        data = limits) +
      coord_polar(theta = "x") +
      scale_x_continuous(
        name = NULL,
        limits = c(0, 360),
        breaks = seq(0, 360, length.out = 5)[-1],
        labels = c("E", "S", "W", "N")
        # minor_breaks = seq(0, 360, length.out = 9)[-1] # if you want to specify semi-cardinals or more
        ) +
      scale_color_manual(
        values = c(R="red", B="blue")
      ) +
      guides(color = "none") +
      theme_minimal() +
      theme(
        panel.grid.minor = element_blank() # suppress minor_breaks
      )
    

    ggplot polar plot with arrows

    You can control much of the arrows by reading ?grid::arrow and changing its options, I'm using the defaults.

    Also, I hard-code the x-limits, required to keep it to a 360 degree circle (since this is really just a polar plot with undefined circular limits).