Я ищу способ объединить geom_segment() с geom_point() и определить position так, чтобы он всегда был связан с linewidth.
library(ggplot2)
# Sample data for geom_segment
segment_data <- data.frame(
y = 1:5,
x = as.POSIXct(c("2024-04-15 08:00:00", "2024-04-15 08:15:00", "2024-04-15 08:30:00", "2024-04-15 08:45:00", "2024-04-15 09:00:00")),
xend = as.POSIXct(c("2024-04-15 09:00:00", "2024-04-15 09:15:00", "2024-04-15 09:30:00", "2024-04-15 09:45:00", "2024-04-15 10:00:00"))
)
# Sample data for geom_point
point_data <- data.frame(
y = c(1, 2, 3, 4, 5), # Adjusted y values for points
x = as.POSIXct(c("2024-04-15 08:05:00", "2024-04-15 08:20:00", "2024-04-15 08:35:00", "2024-04-15 08:50:00", "2024-04-15 09:05:00"))
)
linewidth <- 2
ggplot() +
geom_segment(segment_data, mapping = aes(x = x, xend = xend, y = y),
linewidth = linewidth) +
geom_point(point_data, mapping = aes(x = x, y = y),
shape = 25, color = "black", fill = "red", size = 5,
position = position_nudge(y = linewidth/25))
Это дает желаемый график, где точки по существу являются индикаторами вдоль линий сегмента:
В этом примере linewidth равно 2, а подталкивание — linewidth/25, что очень произвольно. Я ожидал, что linewidth/2 будет достаточно.
Как я могу определить значение y для position_nudge(), чтобы это имело место независимо от значения linewidth?
Спасибо за ваше внимание.
@JonSpring, спасибо за понимание и ответ. Я знал, что это как-то связано с этим, но не мог подобрать правильную терминологию. В идеале я хотел бы включить это в пакет, который я пишу, и позволить пользователям легко размещать индикаторы-маркеры вдоль своих сегментов лучшим способом, чем «продолжать попытки, пока все не будет выглядеть правильно», если это возможно.
Другой подход может заключаться в замене geom_segment на geom_rect и geom_point(shape = 25) на geom_polygon, а затем построить их на основе указанной вами константы масштабирования. Тогда вы сможете составить план за один раз, но с большим количеством предварительных ошибок.





TL;DR: Как говорит Джон Спринг, вам потребуется сочетание единиц данных и абсолютных единиц, чтобы сделать это правильно. Вот формула, которая вам нужна для смещения shape = 25: 0.55 * unit(fontsize, "pt") + unit((lwd * .pt) / .stroke * 0.5, "pt").
Теперь к реальным примерам; первое: давайте настроим таким же образом:
library(ggplot2)
# Sample data for geom_segment
segment_data <- data.frame(
y = 1:5,
x = as.POSIXct(c("2024-04-15 08:00:00", "2024-04-15 08:15:00", "2024-04-15 08:30:00", "2024-04-15 08:45:00", "2024-04-15 09:00:00")),
xend = as.POSIXct(c("2024-04-15 09:00:00", "2024-04-15 09:15:00", "2024-04-15 09:30:00", "2024-04-15 09:45:00", "2024-04-15 10:00:00"))
)
# Sample data for geom_point
point_data <- data.frame(
y = c(1, 2, 3, 4, 5), # Adjusted y values for points
x = as.POSIXct(c("2024-04-15 08:05:00", "2024-04-15 08:20:00", "2024-04-15 08:35:00", "2024-04-15 08:50:00", "2024-04-15 09:05:00"))
)
linewidth <- 2
Затем мы сделаем два графика. Тот, который мы будем использовать позже, и тот, который включает слой точек.
p <- ggplot() +
geom_segment(segment_data, mapping = aes(x = x, xend = xend, y = y),
linewidth = linewidth, colour = "grey50")
p2 <- p +
geom_point(point_data, mapping = aes(x = x, y = y),
shape = 25, color = "black", fill = "red", size = 5)
Из последнего графика мы собираемся извлечь графический объект слоя (grob). Из grob вы можете извлечь графические параметры, необходимые для расчета, например точку fontsize и отрезок lwd.
Эту формулу я получил методом проб и ошибок, но она должна быть довольно последовательной, когда linewidth и size меняются. Мы просто добавляем это к уже существующим координатам y точек, а затем рисуем как пользовательский слой.
segments <- layer_grob(p2, i = 1)[[1]]
points <- layer_grob(p2, i = 2)[[1]]
points$y <- points$y + 0.55 * unit(points$gp$fontsize, "pt") +
unit((segments$gp$lwd * .pt) / .stroke * 0.5, "pt")
p + annotation_custom(points)

Однако учтите: это не работает ни с какой формой. Вы можете видеть, что над линиями плавают круги.
points$pch <- 21
p + annotation_custom(points)

Created on 2024-04-15 with reprex v2.1.0
Чтобы это работало с другими фигурами, измените 0.55 в формуле, чтобы она соответствовала новой фигуре. Теперь, поскольку вы упомянули, что можете написать для этого пакет, я рекомендую просто написать расширение geom, которое вычисляет size/fontsize и linewidth/lwd точно так же, как geom_segment() и geom_point(), а затем применяет это смещение. Таким образом, вам не придется взламывать его с помощью annotation_custom() и тому подобного, так как из-за граней он может стать корявым.
Это так информативно и полезно! Спасибо, я рассмотрю возможность применения формулы к новой геометрии, как вы описали. Оцените развернутый ответ.
из текста ggplot2 мне немного неясно, как обернуть обе существующие геометрии в расширение, чтобы добиться этого. Для этого пакета пользователям необходимо будет иметь возможность указывать сегменты и точки в отдельных функциональных командах. Я вижу выгоду, если мы сможем разгрузить логику, annotation_custom() немного ограничен в гибкости.
Я думаю, что сложность здесь в том, что
linewidthпараметризуется в пространстве графика (например, в пикселях), аposition_nudgeпараметризуется в пространстве координат (например, значения y), поэтому вам понадобится что-то, что соединяет эти два в контексте вашего конкретного графика. . В зависимости от диапазона данных, диапазона координат и разрешения графика каждая единицаyбудет охватывать разное количество пикселей.