When plotting a bar chart, I often add labels to bars to signify the y-value for each bar. However, I run into trouble when the bar becomes too low, making the label unreadable or simply ugly.
library(ggplot2)
df_blood <- data.frame(blood_type = c("O-", "O+", "A-", "A+", "B-", "B+", "AB-", "AB+"),
frequency = c(0.13, 0.35, 0.08, 0.3, 0.02, 0.08, 0.01, 0.02))
ggplot(df_blood, aes(x = blood_type, y = frequency, fill = blood_type)) +
geom_bar(stat = "identity") +
geom_text(aes(label = frequency), color = "blue", vjust = 1, size = 7)
Created on 2021-01-25 by the reprex package (v0.3.0)
Looking at the bar of AB-
we can see that the 0.01
text is exceeding the bar height (at the bar's bottom). In such cases, I'd like to change the vjust
of geom_text()
to 0
.
Here I'm using the same size = 7
as above for geom_text()
:
library(ggplot2)
df_something <- data.frame(something = c("a", "b", "c"),
quantity = c(10000, 7800, 500))
ggplot(df_something, aes(x = something, y = quantity)) +
geom_bar(stat = "identity", fill = "black") +
geom_text(aes(label = quantity), color = "red", vjust = 1, size = 7)
Created on 2021-01-25 by the reprex package (v0.3.0)
Here we see that the bar for c
has the 500
text exceeding the bottom of the bar. So in such case, I'd also like to change geom_text()
's vjust
to 0
, for bar c
only.
Although there are solutions to change vjust
conditionally with a simple ifelse
(see this SO solution) based on the y-value, I'm trying to figure out how to condition vjust
such that it would work regardless of the values on the y scale. Rather, the rule should be that if the bar's height is lower than size of geom_text()
, the text position will move to be on top. Thanks!
EDIT
Based on the discussion below with @Paul, I wonder whether it could be easier to condition vjust
on whether geom_text()
position overlies y = 0
, and if it does, change vjust
to 0
.
EDIT 2
This SO solution (credit to @Paul for finding) seems close enough to what I'm asking. It dynamically changes the size
of geom_text()
to fit bar width, and is working even when resizing the plot. So I think this provides basis to what I'm after, just instead of tweaking size
I need to tweak vjust
, and instead of conditioning it on bar width I need to condition it on bar height. Unfortunately it is too complex for my understanding of ggproto
and alike, so I don't know how to adapt it to my case.
As an out-of-the-box option to achieve your desired result I would suggest to have a look at the ggfittext
package which has some options to put the labels outside of the bars if they don't fit inside or to shrink the labels. Additionally there are also options to add some padding around the labels. However, it uses a no-default sizing policy so you you have to multiply default units by ggplot2::.pt
:
library(ggplot2)
library(ggfittext)
df_something <- data.frame(something = c("a", "b", "c"),
quantity = c(10000, 7800, 500))
ggplot(df_something, aes(x = something, y = quantity)) +
geom_bar(stat = "identity", fill = "black") +
geom_bar_text(aes(label = quantity),
color = "red",
vjust = 1,
size = 7 * ggplot2::.pt,
min.size = 7 * ggplot2::.pt,
padding.x = grid::unit(0, "pt"),
padding.y = grid::unit(0, "pt"),
outside = TRUE)
#> Warning: Ignoring unknown aesthetics: label
df_blood <- data.frame(blood_type = c("O-", "O+", "A-", "A+", "B-", "B+", "AB-", "AB+"),
frequency = c(0.13, 0.35, 0.08, 0.3, 0.02, 0.08, 0.01, 0.02))
ggplot(df_blood, aes(x = blood_type, y = frequency, fill = blood_type)) +
geom_bar(stat = "identity") +
geom_bar_text(aes(label = frequency),
color = "blue",
vjust = 1,
size = 7 * ggplot2::.pt,
min.size = 7 * ggplot2::.pt,
padding.x = grid::unit(0, "pt"),
padding.y = grid::unit(0, "pt"),
outside = TRUE)
#> Warning: Ignoring unknown aesthetics: label