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

Support Hjust and Vjust #69

Closed
bmschmidt opened this issue Feb 24, 2017 · 12 comments
Closed

Support Hjust and Vjust #69

bmschmidt opened this issue Feb 24, 2017 · 12 comments

Comments

@bmschmidt
Copy link

bmschmidt commented Feb 24, 2017

Thanks for a great package! Now that this issue is closed with the capability to repel only along the x or y direction, it might be worth following up on one of the things mentioned in there: (re-)supporting hjust and vjust parameters when movement on that dimension isn't allowed. My particular use case is a slopegraph where I want to have a flush hjust=0 on the left and hjust=1 on the right side for labels--see example. The current version is OK with a nudge parameter, but it would be better to have all labels flush as in the (overlappy) geom_text version below with lines emanating out of the left or right of the text, not the bottom/top.

Sorry if this is now possible and I'm just not seeing how!

slopegraph

overlappy base ggplot version.

slopegraph

@slowkow
Copy link
Owner

slowkow commented Feb 24, 2017

Could I ask you to please try ggplot_build on a plot that uses geom_text_repel? See if you can extract the repelled coordinates and hack a plot that looks the way you want. Then, please share the code and figure.

If that seems to work, then I'll consider whether this is reasonable to try to integrate into the package.

@bmschmidt
Copy link
Author

Sorry for being dense: I'm looking around the return of ggplot_build and can't find the repelled coordinates anywhere, just the unrepelled ones. Any hints? It seems like this issue is related, but I was unable to get any of the code there to run.

@slowkow
Copy link
Owner

slowkow commented Feb 26, 2017

As I worked through the example below, I started to realize that it might be difficult (or impossible) to take the output from ggplot and hack it into the plot you want. Sorry!

Anyway, I think I understand your feature request, but I unfortunately won't be able to work on this any time soon. I'd be happy to review any pull requests that implement this, though.


It turns out to be a bit tricky to get the coordinates out of a plot. For future reference, here's a working example:

library(ggrepel)
library(gtable)

ggplot(mtcars, aes(x = 0, y = mpg)) +
  coord_cartesian(ylim = c(0, 50)) +
  geom_point(color = 'red') +
  geom_text_repel(
    aes(label = rownames(mtcars)),
    direction = "y",
    nudge_x = -0.1
  )

image

grid.force()

pos <- grid.get("textrepelgrob", grep = TRUE, global = TRUE)

# Coordinates on the [0,1] scale (not the original scale of the data).
pos <- data.frame(
  x = sapply(pos, "[[", "x"),
  y = sapply(pos, "[[", "y"),
  x.orig = sapply(pos, "[[", "x.orig"),
  y.orig = sapply(pos, "[[", "y.orig")
)

plot(pos$x, pos$y)
points(pos$x.orig, pos$y.orig, pch = 19)

image

@AliciaSchep
Copy link
Contributor

I've added into my own fork support for hjust and vjust, but don't think it is ready for pull request yet as the resulting plot doesn't look great as the lines end up clashing with the labels:

hjust_example

One way to address this issue would be to have the hjust/vjust parameter affect how the lines are drawn -- e.g. if text is left aligned the line tries to hit left side of text. I don't think this is a great option though because you could have all the text left aligned but to the right of the points, making the lines longer than necessary.

Another way of making it look better could be to have the lines try to hit the outside of the text using the shortest path (rather than trying to connect to center). Possibly the lines could also be chosen to try to minimize conflict with other labels (might help with issue #34 as well). As this type of change in the line behavior would affect the plots with default ggrepel parameters in addition to cases with hjust and vjust set, figured I should ask whether this kind of change might be acceptable? Or if you might have other ideas for how to make the lines look better with hjust/vjust?

@slowkow
Copy link
Owner

slowkow commented Mar 12, 2017

Alicia, thanks so much for taking the time to do this!

Currently, I use the plots in the vignette to test if any changes to the code break anything. If all of the vignettes look ok, then chances are that most plots will work for most people.

I'd be very happy to merge a pull request that includes the hjust/vjust feature and a new line connection algorithm. Or you could do two pull requests -- that's also fine by me, because these features seem to go well together.

Line segments

As you mentioned, the algorithm currently draws the line segment to the center of the label bounding box. It could be modified to choose 1 of 8 points on the label bounding box that is nearest to the data point:

image

Regarding line segment collisions with labels, I like your suggestion to choose 1 of the 8 points that minimizes collisions. Another possible approach is to modify the collision detection loop to trace along the segments and look for collisions with labels, and then apply forces to the labels. I haven't explored this at all.

Label ordering

The order of the labels is not the same as the order of the data points. For example, I would expect "Datsun 710" to appear below "Merc 240D". You can tell which labels are not lined up properly because the line segments are intersecting.

I might suggest changing the way the layout algorithm is initialized. Currently, it is initialized by adding a tiny amount jitter to each label. Next, the layout is set with repulsion and attraction forces.

To better preserve label ordering, the initialization should "zoom in" on the labels, and then run the repulsion and attraction forces. In other words, the x range and y range of the labels should be extended from [0, 1] to [-1, 2]. I haven't explored this at all, but I have an inkling that it might work.

I also wonder if it would help to distribute the labels evenly across the range of the data -- or to nudge the labels to be slightly more evenly distributed than the initial positions. After repositioning more evenly, then the repulsion algorithm might produce a more pleasant layout.

Here's an example:

library(ggrepel)

dat <- mtcars

# Extend the range of the wt variable
wt_range <- diff(range(dat$wt)) * 1.05
dat$wt_extend <- scales::rescale(dat$wt, c(
  min(dat$wt) - wt_range / 2,
  max(dat$wt) + wt_range / 2
))

# Extend the range of the mpg variable
mpg_range <- diff(range(dat$mpg)) * 1.05
dat$mpg_extend <- scales::rescale(dat$mpg, c(
  min(dat$mpg) - mpg_range / 2,
  max(dat$mpg) + mpg_range / 2
))

# Basic plot
p1 <- ggplot(dat) +
  geom_point(aes(wt, mpg), color = 'red') +
  geom_text(aes(wt, mpg, label = rownames(mtcars))) +
  theme_classic(base_size = 16)

# Same plot, but text labels are "zoomed in"
p2 <- ggplot(dat) +
  geom_point(aes(wt, mpg), color = 'red') +
  geom_text(aes(wt_extend, mpg_extend, label = rownames(mtcars))) +
  theme_classic(base_size = 16)

gridExtra::grid.arrange(p1, p2, ncol = 2)

image

Left: Labels are positioned directly on top of the data.
Right: Labels are positioned on an extended range of the data. This initialization might result in a more pleasant layout after applying the repulsion algorithm. I haven't tested this idea!

@AliciaSchep
Copy link
Contributor

Thanks for the detailed response! Yes the label ordering seems to be non-ideal in the example plot. I have started exploring the "zooming" initialization and/or the more even initial spacing... seems promising for that specific plot but not necessarily generalizable (i.e. if the x values are not all the same but you still only wanted force in y direction only). Decreasing the force also seems to help with this specific plot.

Perhaps the line / label collision you mentioned as a possibility could also look for line / line crossings and try to add repulsion to avoid those.... I will explore a little to see if it seems promising!

@slowkow
Copy link
Owner

slowkow commented Nov 3, 2017

@AliciaSchep Hi Alicia, I know you're busy but I just wonder if you have any code that you want to merge into ggrepel. Feel free to send a pull request any time, and thank you for helping out :) I'd be very happy to merge your hjust and vjust features from your fork.

@AliciaSchep
Copy link
Contributor

Hi @slowkow, I've actually recently been revisiting this question -- just put in a pull request! thanks!

@slowkow
Copy link
Owner

slowkow commented Nov 4, 2017 via email

@JoGall
Copy link

JoGall commented Dec 15, 2017

Looking forward to hjust and vjust improving an already-great function!

@slowkow
Copy link
Owner

slowkow commented Dec 15, 2017

@JoGall @bmschmidt I forgot to announce the new feature here! Woops!

Alicia implemented hjust and vjust, and I think it works well. This has not been pushed to CRAN yet, but it is available from Github.

If you'd like to try it out, please install ggrepel from github and leave a comment here to let me know how it goes!

devtools::install_github("slowkow/ggrepel")

Here's an example that you can copy:

https://github.com/slowkow/ggrepel/blob/master/vignettes/ggrepel.md#align-text-labels

Repository owner deleted a comment from JoGall Dec 19, 2017
@slowkow
Copy link
Owner

slowkow commented Feb 16, 2018

Alicia did a great job with this one. I think the issue is resolved.

@slowkow slowkow closed this as completed Feb 16, 2018
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

4 participants