Using geom_text, nudge half the length of a label in ggplot

Viewed 582

I wish to add labels to points on a ggplot. The labels should be below each point. There may be multiple labels per point. If so, they should be left-justified. Each label may be a different length.

For each point, the shortest name should be centered below each point. Thus, I wish to nudge_x = half the length of the shortest name for each point.

How do I determine the length of a label so as to nudge half its value?

Example

library("tidyverse")

df <- tibble(
  x = c("one", "two"),
  y = c(2.5, 1.7),
  company = c("Normal", "Short\nA_bit_longer")
)

company_nudge_x <- -0.1
company_nudge_y <- -0.2


ggplot(df, aes(x = x, y = y, group = x)) +
  geom_point(size = 5) +
  geom_line(aes(group = "x")) +
  coord_cartesian(ylim = c(0.3, 2.7)) +
  
  # Labels
  geom_text(aes(label = company),
            #nudge_x = company_nudge_x,
            nudge_y = company_nudge_y,
            hjust = 0) # left_justify text
1 Answers

A bit of a hack, but in this solution, for each row of data, you can:

  • get the first part of the label (if applicable)
  • count the number of characters
  • determine the nudge value based on that count (you might have to play around with it, adjusting the value for char_nudge)

And then apply that inside the geom_text() function, inside the aesthetics.

Two things to keep in mind:

  1. Because you have a categorical variable on x, you need to convert it to a factor and then an integer in order to be able to add a nudge to it when using it for the position of the geom_text (thankfully, ggplot2 and as.factor() will both order the levels alphabetically);
  2. This works best with a monospace font (you can try and see what happens if you remove the argument family = "mono": the l is not as wide as other letters, which results in a switch in position).
library(tidyverse)

# example dataframe
df <- tibble(
  x = c("one", "two"),
  y = c(2.5, 1.7),
  company = c("Normalllllllllllll", "Short\nA_bit_longerrrrrr")
)
# set constants
company_nudge_y <- -0.2
char_nudge <- 0.03

# augment dataframe
df <- df %>% 
  mutate(comp_small = str_extract(company, "^.+"),
         len_lab = nchar(comp_small),
         nudge_x = -len_lab / 2 * char_nudge)

# plot it
ggplot(df, aes(x = x, y = y, group = x)) +
  geom_point(size = 5) +
  geom_line(aes(group = "x")) +
  coord_cartesian(ylim = c(0.3, 2.7)) +
  # Labels
  geom_text(aes(x = as.integer(as.factor(x)) + nudge_x, # add the nudge
                label = company),
            family = "mono", # monospace font will work better
            nudge_y = company_nudge_y,
            hjust = 0) # left_justify text

Created on 2020-11-27 by the reprex package (v0.3.0)

Related