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:
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.)
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()
The key things here to realise are:
geom_arc
is concerned, 0 degrees occurs on the y axis (at the 12 o'clock position) and positive angles are measured clockwise from there. Often when using trigonometric functions we would think of 0 degrees as being along the x axis and measured counter-clockwise. This is one of the assumptions behind atan2
. What this means is that you need to calculate atan2
as atan2([delta x], [delta y])
rather than the other way round (the documentation says atan2([delta y], [delta x])
, and this was the way your code had it).pi
radians, and in those cases add 2 * pi
onto the smaller of the two angles. This ensures the angle is always below pi radians.