Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple geom_text_repel give overlapping labels #153

Open
dariober opened this issue Apr 27, 2020 · 13 comments
Open

Multiple geom_text_repel give overlapping labels #153

dariober opened this issue Apr 27, 2020 · 13 comments

Comments

@dariober
Copy link

geom_text_repel and geom_label_repel do not "see" labels plotted by other calls of geom_*_repel resulting in overlapping labels.

Here I plot the same text twice using geom_text_repel, once in blue and one in red (this is pointless of course, just for sake of example)

dat <- data.frame(x= 1:10, y= 1:10, text= LETTERS[1:10])

gg <- ggplot(data= dat, aes(x= x, y= y, label= text)) +
    geom_point() +
    geom_text_repel(colour= 'blue') +
    geom_text_repel(colour= 'red') +
    xlim(-10, 30) +
    ylim(-10, 30)

The blue and red labels overlap each other:

image

In real life I'm using both geom_text_repel and geom_label_repel on the same plot and I get overlapping texts when the text from the two calls are close to each other.

Any fix much appreciated!
(Great package anyway!)
Dario


Session info:

R version 3.5.1 (2018-07-02)
Platform: x86_64-conda_cos6-linux-gnu (64-bit)
Running under: Ubuntu 18.04.4 LTS

Matrix products: default
BLAS/LAPACK: /home/dario/miniconda3/envs/tritume/lib/R/lib/libRlapack.so

locale:
 [1] LC_CTYPE=en_GB.UTF-8       LC_NUMERIC=C               LC_TIME=en_GB.UTF-8        LC_COLLATE=en_GB.UTF-8     LC_MONETARY=en_GB.UTF-8   
 [6] LC_MESSAGES=en_GB.UTF-8    LC_PAPER=en_GB.UTF-8       LC_NAME=C                  LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] ggrepel_0.8.2 ggplot2_3.3.0

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.4       digest_0.6.25    withr_2.1.2      crayon_1.3.4     dplyr_0.8.5      assertthat_0.2.1 grid_3.5.1       R6_2.4.1        
 [9] lifecycle_0.2.0  gtable_0.3.0     magrittr_1.5     scales_1.1.0     pillar_1.4.3     rlang_0.4.5      farver_2.0.3     labeling_0.3    
[17] tools_3.5.1      glue_1.3.2       purrr_0.3.3      munsell_0.5.0    compiler_3.5.1   pkgconfig_2.0.3  colorspace_1.4-1 tidyselect_1.0.0
[25] tibble_2.1.3    
@slowkow
Copy link
Owner

slowkow commented Apr 27, 2020

Thanks for opening the issue, sharing code, and sharing a figure.

I would like for ggplot2 to allow each layer to depend on previous layers, but this is not currently supported.

Right now I can't think of a way to work around this issue, but maybe someone else has an idea?

@AMChalkie
Copy link

+1 for this functionality.

@nickdylla
Copy link

+1 eagerly waiting as well...

@slowkow
Copy link
Owner

slowkow commented Apr 7, 2021

A half-baked idea:

What if there's an option geom_text_repel(cascade = TRUE) that tells ggrepel to write the x,y coordinates to a temporary file?

How it might work, step by step:

  1. The first call to geom_text_repel() creates a temporary file. ggrepel sees it is empty. It writes x,y coordinates to it.
  2. The second call to geom_text_repel() reads the temporary file, and sees that it is not empty. So, it adds those x,y coordinates as additional points to repel away from. Then it appends its own x,y coordinates to the file.
  3. The third call to geom_text_repel() reads the temporary file, and sees the x,y coords from the first two calls... etc.

That way, each call to geom_text_repel() cumulatively appends more coordinates to repel away from.

What do you think?

I think it's a bit weird to use a temporary file for this, but I don't know how else to get the ggplot2 layers to "see" each other in a cascading way (kinda like CSS for HTML).

I don't think that ggrepel would have any way to know when the cascading is completed, so the temporary files would not be deleted at the end of the ggrepel calls. Instead, they would continue accumulating indefinitely with additional calls to ggrepel.

@Rubensvingholm
Copy link

Rubensvingholm commented Jun 16, 2021

I'm not knowledgeable enough to know whether the cascade idea would work.
But are there any new solutions to this? (+1) Right now I'm just cycling through seeds until randomness provides a decent result.

@Raoul-Kima
Copy link

Not sure how much you're in contact with / part of the ggplot2 team, but I could imagine "layers being aware of each other" might be something they were or are thinking about themselfes.

@slowkow
Copy link
Owner

slowkow commented Jun 22, 2021

It seems that the ggplot2 Google Group is not the best place to discuss this feature: https://groups.google.com/g/ggplot2/c/OAw_SijqzVg

I created a new post for discussion here: https://community.rstudio.com/t/feature-discussion-layers-aware-of-each-other-in-ggplot2/108156

Feel free to share your ideas, here or on the RStudio Community page.

Pull requests that implement a new feature to address this issue are welcome!

@slowkow
Copy link
Owner

slowkow commented Jun 23, 2021

atusy from RStudio Community shared an excellent code example that we could use to address this issue:

https://community.rstudio.com/t/feature-discussion-layers-aware-of-each-other-in-ggplot2/108156/2

@aphalo I thought you might find this example interesting for your own work — I was not aware of ggplot_add().

library(ggrepel)
library(patchwork)

geom_text_repel2 <- function(...) {
    layer <- ggrepel::geom_text_repel(...)
    layer$ggrepel <- TRUE
    class(layer) <- c("ggrepel", class(layer))
    return(layer)
}

ggplot_add.ggrepel <- function(object, plot, object_name) {
    if (any(do.call(c, lapply(plot$layer, function(x) x$ggrepel)))) {
        warning(
            "There is more than one ggrepel layers. ",
            "This may cause overlap of labels"
        )
    }
   # Optionally, one may modify `object` here.
    NextMethod("ggplot_add")
}
dat <- data.frame(x= 1:10, y= 1:10, text= LETTERS[1:10])

p1 <- ggplot(data= dat, aes(x= x, y= y, label= text)) +
    geom_point() +
    geom_text_repel2(colour = 'blue', seed = 1) +
    labs(title = "one layer")

p2 <- p1 + geom_text_repel2(colour = 'red', seed = 2) +
    labs(title = "two layers")
#> Warning in ggplot_add.ggrepel(object, p, objectname): There is more than one
#> ggrepel layers. This may cause overlap of labels.

p1 + p2

image

@yutannihilation
Copy link
Contributor

This might be related, though I don't remember the details...

tidyverse/ggplot2#3175

@catmoez
Copy link

catmoez commented Jul 12, 2024

Not quite the same issue, but it would be helpful to have an option to distribute the labels relatively evenly over the plot grid.

Many times there is one corner or cluster of the data with all of the text labels and 90% of the plot space has nothing.

@aphalo
Copy link
Contributor

aphalo commented Jul 12, 2024

Not quite what you are after, but the computed-nudge positions from package 'ggpp' can help spread labels more evenly. The closest to what you suggest is position_nudge_to() with x.action="spread" and/or "y.action="spread". Being positions that add nudging they are compatible with geom_text_repel() and geom_label_repel(). HTML help is available. There are also some examples in the package vignette and in the web page Nudging + repulsion with ‘ggrepel’ and ‘ggpp’.

@catmoez
Copy link

catmoez commented Jul 15, 2024

Thanks, that is helpful. Have been doing things manually with a new variable that cuts 90% of labels out, but that doesn't seem optimal

@aphalo
Copy link
Contributor

aphalo commented Jul 15, 2024

In 'ggpp' there are also a couple of stats that can filter labels based on local density of observations: stat_dens1d_labels() and stat_dens1d_labels(). They are designed to work with geom_text_repel() and geom_label_repel(), they replace the character strings mapped to the label aesthetics by "" which the repulsive geoms skip without skipping the original location of the observation, so the other labels are still repulsed away from the obaervations that are not labelled.
If you do the filtering manually, keep all coordinates, but replace the labels to be skipped by "". This ensures that repulsion works for all observations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants