rggplot2ggforce

ggforce geom_arc: How to calculate atan and draw the arc on the intended side


I can't get my head around the calculation of atan2 and placing a geom_arc on the intended side.

Here's some dummy data with four pairs of points ABC. I'd like to draw four arcs, each on the side with the smaller angle and print the angle degrees. Calculating angle dregrees works, but I'd need 1) some switch to correct the atan2 and/or 2) another switch to put the arc on the intended side. Just exchanging start and end has no effect.

tmp <- tibble(xA = c(11, 14, 11, 14), yA = c(8, 8, 7, 7),
              xB = c(10, 15, 10, 15), yB = c(10, 10, 5, 5),
              xC = c(8, 17, 8, 17), yC = c(11, 11, 4, 4))

tmp <- tmp %>%
  # dAB = distance between A and B (same as distance BC)
  mutate(dAB = sqrt((xA - xB)^2 + (yA - yB)^2)) %>%
  mutate(dBC = sqrt((xB - xC)^2 + (yB - yC)^2)) %>%
  # calculate atan AB for arc start
  mutate(arcAB = atan2(yA - yB, xA - xB)) %>%
  # calculate atan BC for arc end
  mutate(arcBC = atan2(yC - yB, xC - xB)) %>%
  # calculate angle degree
  mutate(arc_deg = round((arcBC * (180 / pi)) - (arcAB *  (180 / pi)), 1)) %>%
  mutate(arc_deg = case_when(
    arc_deg >  180 ~ arc_deg - 360,
    arc_deg < -180 ~ arc_deg + 360,
    TRUE           ~ arc_deg)) %>%
  # calculate position for angle dreeg text
  mutate(xAC = (xA + xC) / 2) %>%
  mutate(yAC = (yA + yC) / 2)
tmp

ggplot(tmp) + 
  geom_segment(aes(x = xB, xend = xA, y = yB, yend = yA), size = 1, col = "blue") +
  geom_segment(aes(x = xB, xend = xC, y = yB, yend = yC), size = 1, col = "green") +
  geom_text(aes(x = xB + 0.2, y = yB), label = "B") +
  geom_text(aes(x = xA + 0.2, y = yA), label = "A") +
  geom_text(aes(x = xC - 0.2, y = yC), label = "C") +
  geom_arc(aes(x0 = xB, y0 = yB, r = dAB, start = arcAB, end = arcBC)) + # plus or minus 2*pi
  geom_text(aes(x = xAC, y = yAC, label = paste0(arc_deg, "°"))) +
  coord_fixed() + theme_bw()

The code above only works for the bottom-right arc: arcus

I've seen this and in the top-left arc + 2*pi would have the intended effect, but I think this needs four different ways to cover all cases.

I know this may be more of a mathematical question and considered asking somewhere else, but in the end this is an application of ggforce::geom_arc and others might have a similar problem. (For me it would be perfect, if I'd just have to give the coordinates of A, B and C and a clockwise start and end point.)


Solution

  • It seems you are looking for something like this:

    tmp %>% 
      mutate(r = sqrt((yA - yB)^2 + (xA - xB)^2),
             thetaAB = atan2(xA - xB, yA - yB),
             thetaAB = ifelse(thetaAB < 0, thetaAB + 2 * pi, thetaAB),
             thetaBC = atan2(xC - xB, yC - yB),
             thetaBC = ifelse(thetaBC < 0, thetaBC + 2 * pi, thetaBC),
             direction = ifelse(abs(thetaAB - thetaBC) > pi, "switch", "keep"),
             Start = ifelse(abs(thetaAB - thetaBC) > pi & thetaAB < thetaBC, 
                            thetaAB + 2 * pi, thetaAB),
             End = ifelse(abs(thetaAB - thetaBC) >  pi & thetaBC < thetaAB, 
                          thetaBC + 2 * pi, thetaBC),
             arc_deg = format((End - Start) * 180/pi, digits = 4),
             xAC = (xA + xC) / 2,
             yAC = (yA + yC) / 2) %>%
      ggplot() + 
      geom_segment(aes(x = xB, xend = xA, y = yB, yend = yA), size = 1, col = "blue") +
      geom_segment(aes(x = xB, xend = xC, y = yB, yend = yC), size = 1, col = "green") +
      geom_text(aes(x = xB + 0.2, y = yB), label = "B") +
      geom_text(aes(x = xA + 0.2, y = yA), label = "A") +
      geom_text(aes(x = xC - 0.2, y = yC), label = "C") +
      geom_arc(aes(x0 = xB, y0 = yB, r = r, start = Start, end = End)) + # plus or minus 2*pi
      geom_text(aes(x = xAC, y = yAC, label = paste0(arc_deg, "°"))) +
      coord_fixed() + 
      theme_bw()
    

    enter image description here

    The key things here to realise are: