ggplot2labelpositioninggeom-text

ggplot R, hjust='inward' makes label neither fully inside nor outside


The graphic generated by the code below is updated on a regular basis, and it has been working ok every time, but with the addition of a new data for "Japan", the label is sitting neither outside nor inside of the flipped column as the rest are. Thanks!

    country <- c("AFGHANISTAN", "ALASKA", "CHINA", "ECUADOR", "HAITI", "INDONESIA", "JAPAN",
     "MOROCCO", "BURMA", "NEPAL", "PAPUA", "PHILIPPINES", "TURKEY")
    num <- c(4, 1, 4, 1, 1, 4, 4, 1, 1, 2, 2, 3, 3)
    ytd <- c(1508, 1, 154, 15, 4, 5, 241, 2946, 2, 155, 8, 14, 56703)

    ytd.ct <- data.frame(country, num, ytd)

    ggplot(ytd.ct, aes(x=reorder(country, +ytd), 
                        y=ytd, fill=ytd, fontface = 'plain')) + 
      geom_col() + coord_flip()+
      scale_fill_gradient(low="thistle", high="orchid2", labels = number_format(
    scale=0.001, suffix = 'K', big.mark = ',')) + 
      geom_text(aes(
        label=paste(format(ytd, big.mark=','), ' ' )), 
        size=3.5, vjust=0.4, hjust='inward', color='darkblue') + 
      theme(
        axis.text.x = element_text(angle = 0, size = 10, face = 'plain'),
        axis.text.y = element_text(angle = 0, size = 9.5, face = 'plain'),
        plot.title = element_text(color='black', size= 13.5, 
                              vjust= -1, face= 'bold'),
        plot.subtitle = element_text(color='darkblue', size= 12, 
                                 vjust= -1.3, face= 'bold'),
        legend.title=element_text(face='plain',
                              color="navyblue")) +
      scale_y_continuous(trans = 'log10', labels = number_format(big.mark = ',')) 

Sample graphic for the code attached

I tried adding blank spaces before and after to no avail. I would appreciate very much any help so this label (for Japan) can be either fully inside or outside of that flipped column. Thanks!


Solution

  • The issue is that "inward" aligns text towards the middle of the plot. For labels, which fall at the center or the middle of the plot area, this means that they get centered.

    However, under the hood ggplot2 applies some small tolerance of tol=.001 (see here), when setting the value alignment. As a result, even labels which are slightly to the left or the right of the center get centered too.

    And that's exactly what's happening in your case. After the log transformation of the scale and accounting for the expansion of the scale, the value for Japan falls inside the tolerance when scaled to the range c(0, 1). As a consequence the label for Japan gets centered.

    Here is a small example which mimics your data and illustrates the issue. Similar to Japan the value for category C falls slightly to the right of the center point (i.e. 0.5) but depending on the expansion falls in- or outside the tolerance. Hence, in the first plot the label gets centered, whereas in the second it is aligned to the right.

    library(ggplot2)
    library(scales)
    library(patchwork)
    
    dat <- data.frame(
      x = 10^(0:4) + c(0, rep(1, 4)),
      y = LETTERS[1:5]
    )
    
    log_helper <- function(x, expand) {
      log10_x <- log10(x)
      rescale(log10_x,
        from = range(log10_x) +
          expand * c(-1, 1) * diff(range(log10_x)),
        to = c(0, 1)
      ) |>
        round(5)
    }
    
    p1 <- ggplot(dat, aes(x, y)) +
      geom_col(fill = "grey60") +
      geom_text(aes(label = log_helper(x, .05)),
        hjust = "inward"
      ) +
      scale_x_continuous(trans = "log10") +
      labs(title = "With default expansion of .05")
    
    p2 <- ggplot(dat, aes(x, y)) +
      geom_col(fill = "grey60") +
      geom_text(aes(label = log_helper(x, 0)),
        hjust = "inward"
      ) +
      scale_x_continuous(
        trans = "log10",
        expand = c(0, 0)
      ) +
      labs(title = "Without expansion")
    
    p1 + p2
    

    Now, one option to fix your issue would be to set the alignment manually using the hjust aesthetic and an ifelse:

    # Transformed values of ytd or position of the labels
    sort(setNames(
      log_helper(ytd.ct$ytd, expand = .05),
      ytd.ct$country
    ))
    #>      ALASKA       BURMA       HAITI   INDONESIA       PAPUA PHILIPPINES 
    #>     0.04545     0.10302     0.16059     0.17913     0.21816     0.26464 
    #>     ECUADOR       CHINA       NEPAL       JAPAN AFGHANISTAN     MOROCCO 
    #>     0.27037     0.46380     0.46434     0.50100     0.65330     0.70892 
    #>      TURKEY 
    #>     0.95455
    
    ggplot(ytd.ct, aes(
      y = reorder(country, ytd),
      x = ytd,
      fill = ytd, fontface = "plain"
    )) +
      geom_col() +
      geom_text(
        aes(
          label = number(ytd, big.mark = ","),
          hjust = ifelse(rescale(log10(ytd), to = c(0, 1)) > .5, 1, 0)
        ),
        size = 3.5, vjust = 0.4, color = "darkblue"
      ) +
      scale_fill_gradient(
        low = "thistle", high = "orchid2",
        labels = number_format(
          scale = 0.001, suffix = "K", big.mark = ","
        )
      ) +
      scale_x_continuous(
        trans = "log10",
        labels = number_format(big.mark = ",")
      ) +
      theme(
        axis.text.x = element_text(angle = 0, size = 10, face = "plain"),
        axis.text.y = element_text(angle = 0, size = 9.5, face = "plain"),
        plot.title = element_text(
          color = "black", size = 13.5,
          vjust = -1, face = "bold"
        ),
        plot.subtitle = element_text(
          color = "darkblue", size = 12,
          vjust = -1.3, face = "bold"
        ),
        legend.title = element_text(
          face = "plain",
          color = "navyblue"
        )
      )