slowkow / ggrepel

:round_pushpin: Repel overlapping text labels away from each other in your ggplot2 figures.
https://ggrepel.slowkow.com
GNU General Public License v3.0
1.22k stars 96 forks source link

geom_text_repel with hjust and vjust #188

Open glorious1 opened 3 years ago

glorious1 commented 3 years ago

Summary

This is just a question. I'm labeling points plotted on a map. To optimize position and avoid features in other layers of the map, the data has hJust and vJust columns with values customized for each point. All I want to do is put the labels in those positions, but moved away from the point so the point and text don't overlap. There is no problem with overlap between labels or edge of the map.

When I use the hjust and vjust aesthetics with geom_text_repel, all sorts of weird things happen. Is there any way to respect that, but just push them off the point? Of course the direction they need to move depends on the justification.

Minimal code example

Here is the minimum amount of code needed to demonstrate the issue:

ggplot(...) + geom_text_repel(...)

Here is an image of the output produced by the code:

IMAGE

Suggestions

This is my proposal for how to solve the issue.

Version information

Here is the output from sessionInfo() in my R session:

Paste output here
slowkow commented 3 years ago

@glorious1 Sorry, but I don't understand your question. You might consider sharing code or an image.

glorious1 commented 3 years ago

Thanks very much for the response. I'm making maps that are pretty busy. For labeling cities, I want to control the direction that the label is from the point, both to avoid obscuring other things, and to enhance the legibility of the text.

I started out with geom_text. I can easily control the direction from the point with hjust and vjust aesthetics, but the text sits on the point.

geom_text( data=CITIES.c, 
                 aes( label=City, geometry=geometry, 
                      hjust=hJust, vjust=vJust ),
                 stat = "sf_coordinates",
                 size=2.1 

image

Then I used geom_text repel. That got it nicely spaced from the point, but I couldn't control the direction. In fact, if I run the exact some code twice it can change direction. Look how I was lucky with Crestone one time, and the other time it landed in the mountains. I wish I could control that!

geom_text_repel( data=CITIES.c, 
                 aes( label=City, geometry=geometry ),
                 stat = "sf_coordinates",
                 point.padding=0.05, size=2.1 )

ggrepel_no_just_lucky ggrepel_no_just_unlucky

Then I tried geom_text_repel again, adding hjust and vjust to aes. I didn't expect that to work based on the docs. It caused unpredictable havoc.

geom_text_repel( data=CITIES.c, 
                 aes( label=City, geometry=geometry, 
                      hjust=hJust, vjust=vJust ),
                 stat = "sf_coordinates",
                 point.padding=0.05, size=2.1 )

ggrepel_hvjust Am I missing something easy? If not, I could imagine maybe writing a function that could calculate the amount and direction of nudging in geom_text, based on the values of the hJust and vJust fields. I'm not sure how that would work.

slowkow commented 3 years ago

if I run the exact some code twice it can change direction

set.seed() is the tool you're looking for when you want to get the same random result over multiple runs. This applies to any random function (e.g. runif(), rnorm(), etc.).

For example, if you run this code on your machine, you should (hopefully) get exactly the same layout that I got in this post. Since the size of the figure and font size also affects the layout, your plot might look a bit different.

library(ggrepel)
#> Loading required package: ggplot2
set.seed(1)
d <- data.frame(
  x = runif(10),
  y = runif(10),
  label = letters[1:10]
)
ggplot(d) +
  aes(x, y, label = label) +
  geom_point() +
  geom_text_repel()

Created on 2021-03-15 by the reprex package (v0.3.0)

slowkow commented 3 years ago

Luckily, I left a useful comment in the docs that might answer your question about text justification:

https://github.com/slowkow/ggrepel/blob/f5513ae0707301e41d9486d7fd1b09be95facd72/R/geom-text-repel.R#L23-L27

In other words, we can't have our cake and eat it, too. Either the labels are going to move or not.

Suppose we restrict movement with geom_text_repel(direction = "y"), so that labels can move up and down but not left or right. In this case, it is sensible to allow horizontal justification with hjust. But vjust doesn't make sense anymore because ggrepel is pushing labels up and down.

I hope that gives you some clarity!

slowkow commented 3 years ago

I think you might want to try nudge_x and nudge_y instead of hjust and vjust.

The nudging parameters give you some control to change the initial starting position of each text label before the physical repulsion simulation runs.

aphalo commented 3 years ago

@glorious1 Using values outside the range 0..1 for justification is problematic. You may want to use nudging and justification together. You could disable repulsion if you have manually set the location of the text.