Search code examples
rggplot2ggrepel

Preserving order with geom_text_repel


I'm trying to plot a number-line for floating point numbers (slightly simplified, with a sign bit, one bit for the coefficient (when not zero), and two bits for the exponent). I have this:

library(ggplot2)
library(tibble)
library(ggrepel)

d <- tribble(
  ~repr,             ~number,
 "1.1[2] %*% 2^-11", 2**-3 + 2**-4,
 "1.0[2] %*% 2^-11", 2**-3,
 "1.1[2] %*% 2^-10", 2**-2 + 2**-3,
 "1.0[2] %*% 2^-10", 2**-2,
 "1.1[2] %*% 2^-01", 2**-1 + 2**-2,
 "1.0[2] %*% 2^-01", 2**-1,
 "1.0[2] %*% 2^0",   1,
 "1.1[2] %*% 2^0",   1 + 2**-1,
 "0.0[2] %*% 2^0",   0,
 "1.1[2] %*% 2^01",   2 + 1,
 "1.0[2] %*% 2^01",   2,
 "1.1[2] %*% 2^10",   2**2 + 2**1,
 "1.0[2] %*% 2^10",   2**2,
 "1.1[2] %*% 2^11",   2**3 + 2**2,
 "1.0[2] %*% 2^11",   2**3,

 "-1.1[2] %*% 2^-11", -(2**-3 + 2**-4),
 "-1.0[2] %*% 2^-11", -(2**-3),
 "-1.1[2] %*% 2^-10", -(2**-2 + 2**-3),
 "-1.0[2] %*% 2^-10", -(2**-2),
 "-1.1[2] %*% 2^-01", -(2**-1 + 2**-2),
 "-1.0[2] %*% 2^-01", -(2**-1),
 "-1.0[2] %*% 2^0",   -1,
 "-1.1[2] %*% 2^0",   -(1 + 2**-1),
 "-1.1[2] %*% 2^01",  -(2 + 1),
 "-1.0[2] %*% 2^01",  -2,
 "-1.1[2] %*% 2^10",  -(2**2 + 2**1),
 "-1.0[2] %*% 2^10",  -2**2,
 "-1.1[2] %*% 2^11",  -(2**3 + 2**2),
 "-1.0[2] %*% 2^11",  -2**3
)

ggplot(d) +
  geom_text_repel(aes(x = number, y = -0.1, label = number),
                  parse = TRUE,
                  angle = 0,
                  ylim = c(NA, -0.1)) +
  geom_text_repel(aes(x = number, y = 0.1, label = repr),
                  angle = 0,
                  parse = TRUE, direction = "both", #angle = 90,
                  ylim = c(0.1, NA)) +
  geom_hline(yintercept = 0) +
  coord_cartesian(ylim = c(-2, 2)) +
  labs(x = NULL, y = NULL) +
  theme_classic() +
  theme(axis.line.y = element_blank(),
        axis.ticks = element_blank(),
        axis.text = element_blank())

The result looks like this:

Number line plot

This is almost what I want, but I would prefer if the labels were ordered, left to right, in the same order as the numbers (and I would like to avoid crossing any lines).

Is there a way to avoid crossing any lines with geom_text_repel? I don't mind having a larger spread of labels around zero, where there is a lot of values, I just think it is hard to read the plot as it is, since the labels are scrambled in order the way they are.


Solution

  • Let's add another column to position the text labels:

    d <- d[order(d$number),]
    d$i <- seq(min(d$number), max(d$number), length.out = nrow(d))
    head(d)
    

    Here it is:

                repr  number          i
    -1.1[2] %*% 2^11     -12  -12.00000
    -1.0[2] %*% 2^11      -8  -11.14285
    -1.1[2] %*% 2^10      -6  -10.28571
    -1.0[2] %*% 2^10      -4  -9.428571
    -1.1[2] %*% 2^01      -3  -8.571429
    -1.0[2] %*% 2^01      -2  -7.714286
    

    We don't need ggrepel:

    ggplot() +
    geom_point(
      data = d,
      mapping = aes(x = 0, y = number),
      size = 0.1
    ) +
    geom_text(
      data = d,
      mapping = aes(x = 0.05, y = i, label = repr),
      parse = TRUE, hjust = 0
    ) +
    geom_segment(
      data = d,
      mapping = aes(x = 0, xend = 0.05, y = number, yend = i),
      size = 0.1
    ) +
    scale_y_continuous(
      breaks = c(-12, -8, -6, -4, -3, -2, -1, 0, 1, 2, 3, 4, 6, 8, 12)
    ) +
    coord_cartesian(xlim = c(-0.01, 0.12)) +
    theme_classic(base_size = 14) +
    labs(x = NULL, y = NULL) +
    theme(
      axis.line.y = element_line(size = 0.2),
      axis.ticks.y = element_line(size = 0.2),
      axis.line.x = element_blank(),
      axis.ticks.x = element_blank(),
      axis.text.x = element_blank()
    )
    

    enter image description here