1

In a previous question, I asked about moving the label position of a barplot outside of the bar if the bar was too small. I was provided this following example:

library(ggplot2)
options(scipen=2)
dataset <- data.frame(Riserva_Riv_Fine_Periodo = 1:10 * 10^6 + 1,
                Anno = 1:10)
ggplot(data = dataset,  
aes(x = Anno, 
    y = Riserva_Riv_Fine_Periodo)) + 
geom_bar(stat = "identity", 
          width=0.8, 
          position="dodge") + 
geom_text(aes( y = Riserva_Riv_Fine_Periodo,
                label = round(Riserva_Riv_Fine_Periodo, 0), 
                angle=90, 
                hjust= ifelse(Riserva_Riv_Fine_Periodo < 3000000, -0.1,  1.2)), 
          col="red", 
          size=4, 
          position = position_dodge(0.9))

And I obtain this graph: Histogram

The problem with the example is that the value at which the label is moved must be hard-coded into the plot, and an ifelse statement is used to reposition the label. Is there a way to automatically extract the value to cut?

Michael Harper
  • 14,721
  • 2
  • 60
  • 84

1 Answers1

2

A slightly better option might be to base the test and the positioning of the labels on the height of the bar relative to the height of the highest bar. That way, the cutoff value and label-shift are scaled to the actual vertical range of the plot. For example:

ydiff = max(dataset$Riserva_Riv_Fine_Periodo)

ggplot(dataset, aes(x = Anno, y = Riserva_Riv_Fine_Periodo)) + 
  geom_bar(stat = "identity", width=0.8) + 
  geom_text(aes(label = round(Riserva_Riv_Fine_Periodo, 0), angle=90, 
                y = ifelse(Riserva_Riv_Fine_Periodo < 0.3*ydiff, 
                           Riserva_Riv_Fine_Periodo + 0.1*ydiff,
                           Riserva_Riv_Fine_Periodo - 0.1*ydiff)), 
            col="red", size=4)

enter image description here

You would still need to tweak the fractional cutoff in the test condition (I've used 0.3 in this case), depending on the physical size at which you render the plot. But you could package the code into a function to make the any manual adjustments a bit easier.

It's probably possible to automate this by determining the actual sizes of the various grobs that make up the plot and setting the condition and the positioning based on those sizes, but I'm not sure how to do that.

Just as an editorial comment, a plot with labels inside some bars and above others risks confusing the visual mapping of magnitudes to bar heights. I think it would be better to find a way to shrink, abbreviate, recode, or otherwise tweak the labels so that they contain the information you want to convey while being able to have all the labels inside the bars. Maybe something like this:

library(scales)

ggplot(dataset, aes(x = Anno, y = Riserva_Riv_Fine_Periodo/1000)) + 
  geom_col(width=0.8, fill="grey30") + 
  geom_text(aes(label = format(Riserva_Riv_Fine_Periodo/1000, big.mark=",", digits=0), 
                y = 0.5*Riserva_Riv_Fine_Periodo/1000), 
            col="white", size=3) +
  scale_y_continuous(label=dollar, expand=c(0,1e2)) +
  theme_classic() +
  labs(y="Riserva (thousands)")

enter image description here

Or maybe go with a line plot instead of bars:

ggplot(dataset, aes(Anno, Riserva_Riv_Fine_Periodo/1e3)) +
  geom_line(linetype="11", size=0.3, colour="grey50") +
  geom_text(aes(label=format(Riserva_Riv_Fine_Periodo/1e3, big.mark=",", digits=0)), 
            size=3) +
  theme_classic() +
  scale_y_continuous(label=dollar, expand=c(0,1e2)) +
  expand_limits(y=0) +
  labs(y="Riserva (thousands)")

enter image description here

eipi10
  • 91,525
  • 24
  • 209
  • 285
  • is there a way to specify the colour for the text labels in your first solution based on whether the label is outside or inside the bar? – iouraich Jan 13 '23 at 14:08