4

I am making a bar chart with long axis labels which i need to wrap and right align. The only complication is i need to add a expression to have superscripts.

library(ggplot2)
library(scales) 
df <- data.frame("levs" = c("a long label i want to wrap",
                            "another also long label"),
                 "vals" = c(1,2))

p <- ggplot(df, aes(x = levs, y = vals)) + 
  geom_bar(stat = "identity") +  
  coord_flip() + 
  scale_x_discrete(labels = wrap_format(20))

which produces the desired result:

enter image description here

with properly wrapped text with all labels fully right aligned.

However now I attempt to add superscript using the below code, and the axis text alignment changes:

p <- ggplot(df, aes(x = levs, y = vals)) + 
  geom_bar(stat = "identity") +  
  coord_flip() + 
  scale_x_discrete(labels = c(expression("exponent"^1),
                              wrap_format(20)("another also long label")))

enter image description here

(NB I cannot use unicode as is recommended to others with the same question because it does not work with the font I am required to use).

How can I get the axis text to be aligned right even when one of the axis labels includes an expression?

1 Answer 1

1

It's a strange thing, but if a vector (e.g. a character vector of labels) includes an object created by expression(), the whole vector appears to be treated as an expression:

# create a simple vector with one expression & one character string
label.vector <- c(expression("exponent"^1),
                  wrap_format(20)("another also long label"))

> sapply(label.vector, class) # the items have different classes when considered separately
[1] "call"      "character"

> class(label.vector) # but together, it's considered an expression
[1] "expression"

... and expressions are always left-aligned. This isn't a ggplot-specific phenomenon; we can observe it in the base plotting functions as well:

# even with default hjust = 0.5 / vjust = 0.5 (i.e. central alignment), an expression is 
# anchored based on the midpoint of its last line, & left-aligned within its text block
ggplot() +
  annotate("point", x = 1:2, y = 1) +
  annotate("text", x = 1, y = 1, 
           label = expression("long string\nwith single line break"))+
  annotate("text", x = 2, y = 1, 
           label = expression("long string\nwith multiple line\nbreaks here")) +
  xlim(c(0.5, 2.5))

# same phenomenon observed in base plot
par(mfrow = c(1, 3))
plot(0, xlab=expression("short string"))
plot(0, xlab=expression("long string\nwith single line break"))
plot(0, xlab=expression("long string\nwith multiple line\nbreaks here"))

illustration 1

illustration 2

Workaround

If we can force each label to be considered on its own, without the effect of other labels in the label vector, the non-expression labels could be aligned like normal character strings. One way to do this is to convert the ggplot object into grob, & replace the single textGrob for y-axis labels with multiple text grobs, one for each label.

Prep work:

# generate plot (leave the labels as default)
p <- ggplot(df, aes(x = levs, y = vals)) + 
  geom_bar(stat = "identity") +  
  coord_flip()
p

# define a list (don't use `c(...)` here) of desired y-axis labels, starting with the
# bottom-most label in your plot & work up from there
desired.labels <- list(expression("exponent"^1),
                       wrap_format(20)("another also long label"))

Grob hacking:

library(grid)
library(magrittr)

# convert to grob object
gp <- ggplotGrob(p)

# locate label grob in the left side y-axis
old.label <- gp$grobs[[grep("axis-l", gp$layout$name)]]$children[["axis"]]$grobs[[1]]$children[[1]]

# define each label as its own text grob, replacing the values with those from
# our list of desired y-axis labels
new.label <- lapply(seq_along(old.label$label),
                    function(i) textGrob(label = desired.labels[[i]],
                                         x = old.label$x[i], y = old.label$y[i],
                                         just = old.label$just, hjust = old.label$hjust,
                                         vjust = old.label$vjust, rot = old.label$rot,
                                         check.overlap = old.label$check.overlap,
                                         gp = old.label$gp))

# remove the old label
gp$grobs[[grep("axis-l", gp$layout$name)]]$children[["axis"]]$grobs[[1]] %<>%
  removeGrob(.$children[[1]]$name)

# add new labels
for(i in seq_along(new.label)) {
  gp$grobs[[grep("axis-l", gp$layout$name)]]$children[["axis"]]$grobs[[1]] %<>%
    addGrob(new.label[[i]])
}

# check result
grid.draw(gp)

result

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you so much for the incredibly detailed and helpful post! this solved my problem perfectly!
Can you use variables inside expression?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.