Justify individual axis labels in bold using ggplot2
Asked Answered
H

2

6

Question adapted from this question and solution: Highlighting individual axis labels in bold using ggplot2

I would like to selectively justify the horizontal axes labels depending on meeting a criteria. So borrowing from the above question and answer I've set up an example:

require(ggplot2)
require(dplyr)
set.seed(36)
xx<-data.frame(YEAR=rep(c("X", "Y"), each=20),
           CLONE=rep(c("A", "B", "C", "D", "E"), each=4, 2),
           TREAT=rep(c("T1", "T2", "T3", "C"), 10),
           VALUE=sample(c(1:10), 40, replace=T))

# Simple plot with factors on y axis
ggplot(xx, aes(x = VALUE, y=CLONE, fill=YEAR)) + 
    geom_bar(stat="identity", position="dodge") +
    facet_wrap(~TREAT)

enter image description here

Ok so I adopted function from the above question + answer to generate a vector of justifications:

# Modify to control justification
colorado2 <- function(src, boulder) {
    if (!is.factor(src)) src <- factor(src)                   
    src_levels <- levels(src)                                 
    brave <- boulder %in% src_levels                         
    if (all(brave)) {                                         
        b_pos <- purrr::map_int(boulder, ~which(.==src_levels)) 
        b_vec <- rep(0.2, length(src_levels))               
        b_vec[b_pos] <- 0.9                                 
        b_vec                                                  
    } else {
        stop("All elements of 'boulder' must be in src")
    }
}

# Redraw the plot with modifcation
ggplot(xx, aes(x = VALUE, y=CLONE, fill=YEAR)) + 
    geom_bar(stat="identity", position="dodge") +
    facet_wrap(~TREAT) +
    theme(axis.text.y=element_text(hjust=colorado2(xx$CLONE, c("A", "B", "E"))))

I'm getting this unfortunate mess: enter image description here

The labels are justified in the direction I want - but taking up far too much of the plot for reasons I cannot figure out. How do I fix this ?

Hotchpot answered 16/1, 2018 at 16:49 Comment(0)
S
2

I did some digging. The problem is with how ggplot sets the grob width of the y axis grob. It assumes that hjust is the same across all labels. We can fix this with some hacking of the grob tree. The following code was tested with the development version of ggplot2 and may not work as written with the currently released version.

First, a simple reproducible example:

p <- ggplot(mpg, aes(manufacturer, hwy)) + geom_boxplot() + coord_flip() + 
  theme(axis.text.y = element_text(hjust = c(rep(1, 10), rep(0, 5))))
p # doesn't work

enter image description here

The problem is that the grob width of the axis grob gets set to the entire plot area. But we can manually go in and fix the width. Unfortunately we have to fix it in multiple locations:

# get a vector of the y labels as strings
ylabels <- as.character(unique(mpg$manufacturer))

library(grid)
g <- ggplotGrob(p)

# we need to fix the grob widths at various locations in the grob tree
g$grobs[[3]]$children[[2]]$widths[1] <- max(stringWidth(ylabels))
g$grobs[[3]]$width <- sum(grobWidth(g$grobs[[3]]$children[[1]]), grobWidth(g$grobs[[3]]$children[[2]]))
g$widths[3] <- g$grobs[[3]]$width

# draw the plot
grid.newpage()
grid.draw(g)

enter image description here

The axis-drawing code of ggplot2 could probably be modified to calculate width like I did here from the outset, and then the problem would disappear.

Stichomythia answered 16/1, 2018 at 23:40 Comment(0)
S
1

The method you employ is hacking the underlying grid graphics engine and results may not always be obvious. See here for an answer that goes into the grob tree and fixes things.

However, for the problem as stated in the question, there is a simple solution that doesn't require any hacking. Just make labels that have some trailing spaces. I did this manually here, but you could also write a function that does this.

ggplot(xx, aes(x = VALUE, y=CLONE, fill=YEAR)) + 
  geom_bar(stat="identity", position="dodge") +
  scale_y_discrete(breaks = c("A", "B", "C", "D", "E"),
                   labels = c("A    ", "B    ", "C", "D", "E    ")) +
  facet_wrap(~TREAT)

enter image description here

Stichomythia answered 16/1, 2018 at 21:41 Comment(2)
Eh didn't realise that solution was hacking anything ! So the problem I see with your solution is alignment. In my real data its not letters its words of different lengths. Suppose words are: c("beer", "heineken", "guinness", "wine", "merlot", "chardonnay", "whiskey", "middelton","jameson") and I want beer, wine and whiskey left justified and others right justified -they need to line up left or rightHotchpot
I posted a second answer that addresses this.Stichomythia

© 2022 - 2024 — McMap. All rights reserved.