I am making a horizontal bar plot with ggplot2
, with labels to the right of the bars. Hoe do I leave enough room for the labels so they don't fall off the chart?
This question has been asked many times before, but my question is about automatically, that means without manual adjusting, the space next to a barplot to leave enough room for labels.
The use case is a shiny app where:
- we don't know the width of the bars ahead of time
- we don't know the length of the text labels
- we don't know the text size
Example:
library(ggplot2)
data <- data.frame(
weight = c("short","longer label","medium lab"),
speed = sample(50:150,3)
)
ggplot(data, aes(x = weight, y = speed, label = weight))
coord_flip(clip = 'off')
theme_minimal()
geom_bar(stat = "identity")
geom_text(hjust = -0.1, size = 4)
ylim(c(0, 1.07 * max(data$speed)))
Re-run the code and you will see that the label sometimes falls off the chart on the right).
My solution so far which "kind of" works is to have some estimator for the ylim
multiplier (here, 1.07) to leave enough room. I can of course use a really high value but then we create too much whitespace.
I have also attempted to calculate the width of the grob via grid::grobWidth
, largely based on this post:
Let's extract the data layer and measure the range of the plot
dat <- p$data[order(factor(p$data$weight)),]
range <- range(layer_scales(p)$y$limits)
Now we get the positions of the labels and measure their size in pixels:
label_pos <- dat$speed
str_width <- textshaping::shape_text(
strings = dat$weight,
size = p$layers[[2]]$aes_params$size * .pt)$metrics$width
The string width is in pixels, so we need to convert this to milimetres:
str_width <- grid::convertWidth(unit(str_width/72, 'in'), 'mm', TRUE)
Now we can measure the panel and work out what the number of units per mm the x axis represents:
pan <- grid::convertWidth(unit(1, 'npc') - sum(ggplotGrob(p)$widths[-5]),
'mm', TRUE)
units_per_mm <- 2 * diff(range) / pan
Finally, we can add the correct number of units, dictated by the string width, onto the position of the strings to find where the rightmost string ends. This will be our new upper limit for the plot:
range <- c(range[1], max(str_width * units_per_mm label_pos))
p$scales$scales[[1]] <- scale_y_continuous(limits = range)
So our plot now looks like this:
p
CodePudding user response:
An option could be by creating breaks in your labels using \n
to save space like this:
library(ggplot2)
data <- data.frame(
weight = c("short","longer label","medium lab"),
speed = sample(50:150,3)
)
data$weight2 <- sub("\\s", "\n", data$weight)
ggplot(data, aes(x = weight, y = speed, label = weight2))
coord_flip(clip = 'off')
theme_minimal()
geom_bar(stat = "identity")
geom_text(hjust = -0.1, size = 4)
ylim(c(0, 1.07 * max(data$speed)))
Created on 2023-01-12 with reprex v2.0.2