ggforce geom_arc:如何计算atan并在预期的一侧绘制弧线

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

我无法理解 atan2 的计算并在预期的一侧放置 geom_arc

这是一些具有四对点 ABC 的虚拟数据。我想画四个圆弧,每个圆弧在角度较小的一侧并打印角度。计算角度 dregrees 有效,但我需要 1) 一些开关来纠正 atan2 and/or 2) 另一个开关将弧放在预期的一侧。只是交换 startend 没有效果。

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

上面的代码只适用于右下角的圆弧:

我已经看到 并且在左上角的圆弧中 + 2*pi 会达到预期的效果,但我认为这需要四种不同的方式来涵盖所有情况。

我知道这可能更像是一个数学问题,并考虑在其他地方提问,但最终这是 ggforce::geom_arc 的应用,其他人可能有类似的问题。 (对我来说这将是完美的,如果我只需要给出 A、B 和 C 的坐标以及顺时针方向的起点和终点。)

看来您正在寻找这样的东西:

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

这里要实现的关键是:

  • geom_arc而言,y轴(在12点钟位置)出现0度,正角度从那里顺时针测量。通常在使用三角函数时,我们会将 0 度视为沿 x 轴并逆时针测量。这是 atan2 背后的假设之一。这意味着您需要将 atan2 计算为 atan2([delta x], [delta y]) 而不是相反(文档中说 atan2([delta y], [delta x]),这就是您的代码所采用的方式)。
  • 由于您希望测量两条线之间的最小角度(即小于 pi 弧度的角度),您需要找出角度之间的差异中哪些角度大于 pi 弧度,并且在这些情况将 2 * pi 添加到两个角度中较小的一个。这确保角度始终低于 pi 弧度。