1

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()
Lisa
  • 21
  • 1
  • Is there a reason you are limiting to just `grid` graphics? I see a lot more questions (and people answering) about `ggplot2` and base graphics. – r2evans Aug 29 '23 at 12:31
  • I don’t mind using any package that will let me get a good publication quality graphic. I know grid and ggplot2 could both give me something that looks good (I need to add labels and different fill colours to the circle too) but I haven’t found a ggplot2 solution either. – Lisa Aug 30 '23 at 13:10

1 Answers1

0

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

r2evans
  • 141,215
  • 6
  • 77
  • 149