Consider a plot with a segment/line and a text/label. I'd like the text to overlay the segment such that the text does not overlap the segment.
I tried using geom_label
but I still want the background to be the same, just the other objects to be removed around the text. I also tried with geomtextpath
but couldn't get the text to be horizontal. Any ideas?
seg <- data.frame(x = 1, xend = 1, y = 2, yend = 3)
plot1 <- ggplot(seg) +
aes(x = x, xend = xend, y = y, yend = yend) +
geom_segment() +
geom_text(aes(y = (y + yend) / 2), label = "Hello", size = 10) +
labs(title = "geom_text",
subtitle = "The text is horizontal but the segment should\nbe cut around the text.")
library(geomtextpath)
plot2 <- ggplot(seg) +
aes(x = x, xend = xend, y = y, yend = yend) +
geom_textsegment(label = "Hello", size = 10) +
labs(title = "geomtextpath",
subtitle = "The segment is correctly overlaid but\nI need the text to be horizontal")
seg2 <- data.frame(x = c(1, 1), xend = c(1, 1), y = c(2, 2.55), yend = c(2.45, 3))
plot3 <- ggplot(seg2) +
aes(x = x, xend = xend, y = y, yend = yend) +
geom_segment() +
geom_text(aes(y = 2.5), label = "Hello", size = 10) +
labs(title = "manual geom_segment",
subtitle = "This is the expected output, but it is done\nmanually. I need a scalable solution.")
library(patchwork)
plot1 + plot2 + plot3
Without writing a new Geom
ggproto object (or adding this as a feature to geomtextpath), it will be difficult to get a fully functional geom layer. However, we can use geomtextpath to generate the broken line by making its text invisible, and getting the height of the break correct by shrinking the invisible text according to its width:height ratio. Then we just add a text label in the middle.
Note that this means the x, y, xend, yend and label need to be passed rather than being mapped as aesthetics, so it acts more like an annotation layer than a true geom layer:
library(geomtextpath)
#> Loading required package: ggplot2
geom_segment_text <- function(label = NULL, data = NULL, mapping = NULL,
inherit.aes = TRUE, x, xend, y, yend, ...,
size = 11/.pt, linecolour = "black") {
df <- data.frame(x = x, y = y, xend = xend, yend = yend, label = label)
ts <- textshaping::shape_text(label)$metric
ratio <- ts$height / ts$width * 0.6
list(
geom_segment(aes(x, y, xend = xend, yend = yend), data = df, colour = NA),
layer(geom = "text", stat = "identity", data = df,
mapping = aes((x + xend)/2, (y + yend)/2, label = label),
position = "identity",
params = list(size = size, ...), inherit.aes = inherit.aes),
layer(geom = "textsegment", stat = "identity", data = df,
mapping = aes(x, y, xend = xend, yend = yend, label = label),
position = "identity",
params = list(colour = NA, size = size * ratio,
linecolour = linecolour, padding = unit(0, "mm"), ...),
inherit.aes = inherit.aes)
)
}
This allows:
ggplot() +
geom_segment_text(label = "Hello", size = 10, x = 1, y = 2, xend = 1, yend = 3)
We can see that the line breaks scale appropriately if the text size is changed. Crucially, because we are using geomtextpath, the spacing of the lines around the text remain constant if the image is resized:
ggplot() +
geom_segment_text(label = "Hello", size = 20, x = 1, y = 2, xend = 1, yend = 3)
Created on 2022-10-18 with reprex v2.0.2