Override Horizontal Positioning with Ggrepel

Override horizontal positioning with ggrepel

TL;DR: probably a bug

Long answer:

I think it might be a bug in the code. I checked the gtable of the plot you made, wherein the hjust was specified numerically and correctly:

# Assume 'g' is the plot saved under the variable 'g'
gt <- ggplotGrob(g)
# Your number at the end of the geom may vary
textgrob <- gt$grobs[[6]]$children$geom_text_repel.textrepeltree.1578
head(textgrob$data$hjust)
[1] 1 0 1 0 1 0

Which got me thinking that (1) the plot can't be fixed by messing around in the gtable and (2) the drawtime code for the textrepeltree class of grobs may contain some errors. This makes sense, since the labels are repositioned when the plot device is resized. So when we look at the makeContent.textrepeltree() code in the link you provided, we can see that the hjust parameter is passed on to makeTextRepelGrobs(). Let's have a look at the relevant formals:

makeTextRepelGrobs <- function(
...other_arguments...,
just = "center",
...other_arguments...,
hjust = 0.5,
vjust = 0.5
) { ...body...}

We can see that hjust is a valid argument, but there also exists a just argument, which is an argument that is not passed on from makeContent.textrepeltree().

When we look at the function body there are these two lines:

  hj <- resolveHJust(just, NULL)
vj <- resolveVJust(just, NULL)

Where resolveH/VJust are imported from the grid package. The resolveHJust() essentially checks whether the second argument is NULL and if that is true, default to the first argument, otherwise return the second argument. You can see that the hjust that was passed on to makeTextRepelGrobs() does not get passed to resolveHJust(), and this seems to be the point where your hjust parameter is dropped unexpectedly.

Further down the code is where the actual text grobs are made:

  t <- textGrob(
...other_arguments...
just = c(hj, vj),
...other_arguments...
)

I imagine that the fix would be relatively straightforward: you would just have to supply hjust as the second argument to resolveHJust(). However, since that makeTextRepelGrobs() is internal to ggrepel and does not get exported, you would have to copy a lot of extra code to get this to work. (Not sure if only copying the makeTextRepelGrob() would be sufficient, haven't tested this)

All of this leaves me to conclude that the hjust that you specified in geom_text_repel() gets lost at the last moment of drawtime by the makeTextRepelGrobs() internal function.

How to horizontally align repelled labels for overlapping points

For these data, geom_text_repel might be overkill. You can achieve similar spacing with geom_label. Only the "2005-2009" data point needs a different horizontal alignment from the rest, which can be be accomplished through the hjust parameter:

ggplot(df, aes(x=I, y=G/100, label=interval)) + geom_point() + 
geom_label(aes(label=interval, hjust = ifelse(interval == '2005-2009', 1, 0)), size=ts, label.padding = unit(0.3, "lines"), fill = NA, label.size = NA) +
scale_x_continuous(limits=c(-1,1), breaks=c(-1,0,1), labels=c("", 0, ""), expand=c(0,0)) +
scale_y_continuous(limits=c(0,1), breaks=c(0,.5,1), expand=c(0,0)) +
geom_hline(yintercept=0.5) + geom_vline(xintercept=0) +
annotate("text", size=ts, x=0.02, y=0.03, label="0", hjust=0) +
annotate("text", size=ts, x=0.97, y=0.03, label="I", hjust=1, fontface="italic") +
annotate("text", size=ts, x=0.02, y=0.47, label="0.5", hjust=0) +
annotate("text", size=ts, x=0.02, y=0.97, label="G", hjust=0, fontface="italic") + pt

Sample Image

Modify outwards label position in ggplot - avoid overlap between node and label

Solution:
Use ggrepel, change outward for inward

ggplot(ggnetwork(net1 ) )+
geom_edges(data=posData1, aes(x = xorig, y = yorig, xend = xtarg, yend = ytarg),
arrow = arrow(length = unit(10, "pt"), type = "closed")
) +
geom_nodes(aes(x=xpos, y=ypos ), size = 4) +
geom_nodelabel_repel(aes(x=xpos, y=ypos, label = vertex.names ),
hjust="inward",
vjust="inward"
,fontface = "italic", size=3
) + theme_blank()

Sample Image

Related:
https://github.com/slowkow/ggrepel/issues/191

ggrepel together with geom_smooth

One way you could do this is to get the last (or first etc. as desired) fitted value for each continent in a summary label data frame, then use that in ggrepel:

library(tidyverse)
library(gapminder)
library(ggrepel)
library(broom)

label_df <- gapminder |>
nest(data = -continent) |>
mutate(model = map(data, ~loess(lifeExp ~ year, .x)),
augmented = map(model, augment),
fitted = map(augmented, ".fitted") |> map_dbl(last),
year = map(data, "year") |> map_int(last)) |>
select(continent, fitted, year)

ggplot(gapminder, aes(year, lifeExp, color = continent)) +
geom_line(size = .1, alpha = .2) +
guides(color = "none") +
theme_minimal() +
geom_smooth(aes(color = continent), se = F, method = "loess") +
geom_label_repel(aes(year, fitted, label = continent), data = label_df)

Sample Image

Created on 2022-07-03 by the reprex package (v2.0.1)

Justifying lines of text within individual ggrepel labels

This has now been addressed in the development version of ggrepel (version 0.8.1.9000).

library(ggplot2)
devtools::install_github("slowkow/ggrepel")

p <- ggplot() +
coord_cartesian(xlim=c(0,1), ylim=c(0,1)) +
theme_void()
p

labelInfo <- data.frame(x=c(0.45,0.55), y=c(0.5,0.5),
g=c("I'd like very much to be\nright justified","And I'd like to be\nleft justified"))

p + geom_label_repel(data=labelInfo, aes(x,y,label=g), hjust=c(1,0),
box.padding = 0.5, point.padding = 0.75,
nudge_x = c(-0.05,0.05), nudge_y = 0, direction="x",
arrow=arrow(length=unit(2,"mm"), ends="last", type="closed"))

Sample Image

ggrepel: Repelling text in only one direction, and returning values of repelled text

ggrepel version 0.6.8 (Install from GitHub using devtools::github_install) now supports a "direction" argument, which enables repelling of labels only in "x" or "y" direction.

repelPlot2 <- ggplot(data) + geom_text_repel(aes(x, y, label = label), segment.size = 0, direction = "y") + theme_classic(base_size = 16)

Getting the y values is harder -- one approach can be to use the "repel_boxes" function from ggrepel first to get repelled values and then input those into ggplot with geom_text. For discussion and sample code of that approach, see https://github.com/slowkow/ggrepel/issues/24. Note that if using the latest version, the repel_boxes function now also has a "direction" argument, which takes in "both","x", or "y".

Use geom_label_repel only for certain observations?

You could do something like this to choose the labels you want:

library(tidyverse)
library(ggrepel)

df <- tribble(~team, ~aay, ~epa,
"LA", 8, 5,
"PIT", 6, -2,
"KC", 7, 5,
"DAL", 7, 5
)

# Select desired labels
labels <- df |> filter(team %in% c("KC", "DAL"))

df |>
ggplot(aes(aay, epa)) +
geom_point() +
geom_label_repel(aes(label = team), data = labels, force = 20) +
xlim(c(0, 10)) +
ylim(c(-8, 8))

Sample Image

Created on 2022-05-25 by the reprex package (v2.0.1)



Related Topics



Leave a reply



Submit