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

Feature request: geom_sf_label_repel(), geom_sf_text_repel() #111

Open
yutannihilation opened this issue Jul 15, 2018 · 20 comments
Open

Feature request: geom_sf_label_repel(), geom_sf_text_repel() #111

yutannihilation opened this issue Jul 15, 2018 · 20 comments

Comments

@yutannihilation
Copy link
Contributor

Summary

ggplot2 will (hopefully) get geom_sf_label() and geom_sf_text() for labeling sf objects. (c.f. tidyverse/ggplot2#2742 (comment))
Are you interested in implementing the _repel version of these? Labelling sf objects is a very common task so I'm sure many people will find geom_sf_label_repel() useful.

If you feel ggrepel is not the right place for them, they will probably be implemented in ggforce.

Minimal code example

Proof of Concept is here: https://yutannihilation.github.io/ggsflabel/#geom_label_repel-for-sf

Suggestions

Note that this can be addressed after geom_sf_label() and geom_sf_text() are implemented in ggplot2. So, please stay tuned on tidyverse/ggplot2#2742! (I will do my best to finish it as soon as possible)

Version information

N/A

@slowkow
Copy link
Owner

slowkow commented Jul 15, 2018

Yes, I'd be interested to include those functions in ggrepel if you think it makes sense, and we're all in agreement across ggplot2, ggrepel, and ggforce. Looks like you have a great start already! Thanks for opening the issue!

I think I need to study the *_sf_* functions a bit to figure out exactly what is going on. I would have assumed that geom_text_repel() should be sufficient without an extra function for spatial data, but clearly this is not correct.

@yutannihilation
Copy link
Contributor Author

Thanks for the quick reply. Glad to hear that!

I would have assumed that geom_text_repel() should be sufficient without an extra function for spatial data

I think you are right in some sense, but, you need to extract the coordinates by yourself (c.f. tidyverse/ggplot2#2111) so we naturally want some shortcut for this. That is, *_sf_* functions, in my understanding :)

@yutannihilation
Copy link
Contributor Author

yutannihilation commented Aug 29, 2018

Now geom_sf_text_repel() and geom_sf_label_repel() are basically possible with stat_sf_coordinates(). Example:

# github version of ggplot2
library(ggplot2)

nc <- sf::st_read(system.file("shape/nc.shp", package="sf"))
#> Reading layer `nc' from data source `/Library/Frameworks/R.framework/Versions/3.5/Resources/library/sf/shape/nc.shp' using driver `ESRI Shapefile'
#> Simple feature collection with 100 features and 14 fields
#> geometry type:  MULTIPOLYGON
#> dimension:      XY
#> bbox:           xmin: -84.32385 ymin: 33.88199 xmax: -75.45698 ymax: 36.58965
#> epsg (SRID):    4267
#> proj4string:    +proj=longlat +datum=NAD27 +no_defs
#> Reading layer `nc' from data source `/path/to/sf/shape/nc.shp' using driver `ESRI Shapefile'
#> Simple feature collection with 100 features and 14 fields
#> geometry type:  MULTIPOLYGON
#> dimension:      XY
#> bbox:           xmin: -84.32385 ymin: 33.88199 xmax: -75.45698 ymax: 36.58965
#> epsg (SRID):    4267
#> proj4string:    +proj=longlat +datum=NAD27 +no_defs

ggplot(nc) +
  geom_sf() +
  ggrepel::geom_label_repel(
    data = head(nc),
    aes(label = NAME, geometry = geometry),
    stat = "sf_coordinates",
    min.segment.length = 0
  )
#> Warning in st_point_on_surface.sfc(sf::st_zm(x)): st_point_on_surface may
#> not give correct results for longitude/latitude data

Created on 2018-09-04 by the reprex
package
(v0.2.0).

@slowkow
Copy link
Owner

slowkow commented Sep 4, 2018

@yutannihilation Do you think we should create two new functions?

  • geom_sf_text_repel()
  • geom_sf_label_repel()

I suppose you could say they are not needed anymore, since you've shown that we can use stat = "sf_coordinates" successfully for many types of figures. On the other hand, it would be convenient to have a wrapper.

What do you think? I think it makes sense to add them.

Whether or not we add two more functions, I think the vignette for ggrepel needs an example showing how to put repulsive labels on a sf plot.

If you're interested, I'd be very happy to review your pull request and merge it in. If you prefer, I can try to do it myself, but I'd appreciate your comments to make sure I'm writing the functions the way you'd expect.


As an aside, you're also making me wonder if I should think about a stat like stat_repel() to enable repulsive positioning for other types of elements besides text boxes... I think I need to spend more time studying the ggplot2 code before I'm ready for this.

@yutannihilation
Copy link
Contributor Author

Yes, I think these functions are worth adding, of course! I want to implement them if you are ok (Sorry for not yet starting on this...).

While it seems easy to implement them as simple wrappers, I'm wondering if I can add an option to prevent labels to overwrap the geometry. Maybe is this the same thing as your concept of stat_repel()? I too have to study the ggplot2 code. 🤔

@slowkow
Copy link
Owner

slowkow commented Sep 5, 2018

Ok, feel free to get started whenever you have time. Thanks for your help!

I like the idea of avoiding collision with the shape of the geometry. This might be a challenge to implement, but I have an inkling that there might be library we can use to do that.

@yutannihilation
Copy link
Contributor Author

Thanks, I will try :)

@kendonB
Copy link

kendonB commented May 15, 2019

I am facing problems trying to get geom_label_repel with a geom_sf. One neat feature would be to also have it repel from sf features themselves. In my case, I'm trying to map NZ regions with the labels falling in the ocean.

@bjsmith
Copy link

bjsmith commented Jan 20, 2020

Working on the same problem as @kendonB .

@CeresBarros
Copy link

Any news on this front? have geom_sf_label/text_repel() been implemented?
Thanks :)

@slowkow
Copy link
Owner

slowkow commented Oct 20, 2021

@CeresBarros Did you try the code example by @yutannihilation ?

Did it meet your needs?

@CeresBarros
Copy link

I did not - I wasn't sure if that was the current "solution". I'll try it :)

@CeresBarros
Copy link

Just tried it, works great, thanks!

@francisbarton
Copy link
Contributor

francisbarton commented Dec 8, 2021

Just come across this issue - getting "Error: geom_label_repel requires the following missing aesthetics: x and y" message when using geom_label_repel with a geom_sf plot.
I was really confused because there's nothing in the ggrepel docs saying anything about geom_label_repel requiring x and y aesthetics!
Searching the web for that error message didn't get me any useful info, so I came here to search.

@yutannihilation 's solution works great, but I do think this should be covered in the ggrepel examples vignette. So that future users don't have to hunt around for the fix.
Would you welcome a PR to your vignette to include an example of this?

@slowkow
Copy link
Owner

slowkow commented Dec 8, 2021

@francisbarton Contributions to the documentation are welcome! Please feel free to make a PR with your suggested changes or additions.

@fariadamasceno
Copy link

Hi,
just to let you know that I would be one more who would appreciate the wrapper functions. =)
The code from @yutannihilation worked nicely, thanks!

@slowkow
Copy link
Owner

slowkow commented Apr 6, 2023

I'd be happy to review and merge a pull request that resolves this issue.

@Steve-Koller
Copy link

Not sure if this is the right place to bring this up, but I'm wondering if it's possible to selectively apply geom_sf_label_repel() to certain features in an sf object and geom_sf_label to other features in the same object? I'm currently trying to do this but unable. Can share photos or a reprex if that would help.

Below is sample code, where "geom_repel_states" is a vector with a few states' FIPS codes. These are the states whose labels I'd like to repel, since their labels overlap when including in geom_sf_label().

Thank you!

example_plot = ggplot(example_sf) + 
    geom_sf(color = "black", aes(fill = example_variable_name)) +
    geom_sf_label_repel(aes(label = ifelse(example_sf$state_fips %in% geom_repel_states, example_variable_name, ""))) + 
    geom_sf_label(aes(label=ifelse(!(example_sf$state_fips %in% geom_repel_states), example_variable_name, ""))) 
  
  example_plot

@slowkow
Copy link
Owner

slowkow commented Apr 29, 2023

@Steve-Koller This isn't really the best place to ask (better would be a new issue, best would be a new question on Stackoverflow), but since you asked...

Each geom_*() function takes a data argument. You can use geom_text_repel(data = ...) to plot a subset of the original data. The ... might be another dataframe like my_subdata or it might also be a function like function(d) d %>% filter(my_condition).

@slowkow
Copy link
Owner

slowkow commented May 5, 2023

We added an example to the ggrepel documentation. Feel free to comment below with your thoughts, and let me know if you believe we need to keep this issue open.

https://ggrepel.slowkow.com/articles/examples.html#label-sf-objects

# thanks to Hiroaki Yutani 
# https://github.com/slowkow/ggrepel/issues/111#issuecomment-416853013

library(ggplot2)
library(sf)

nc <- sf::st_read(system.file("shape/nc.shp", package="sf"), quiet = TRUE)

ggplot(nc) +
  geom_sf() +
  ggrepel::geom_label_repel(
    data = head(nc),
    aes(label = NAME, geometry = geometry),
    stat = "sf_coordinates",
    min.segment.length = 0
  )

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

8 participants