After digging through the graphical objects (grobs) associated with the plot, I came across a potential hack to get around the issue. While not as elegant as Z.Lin's solution, I wanted to share it for educational purposes.
We begin by retrieving grobs with
gt <- ggplotGrob( g + facet_wrap( ~V3, scales = "free" ) )
## TableGrob (11 x 11) "layout": 20 grobs
## z cells name grob
## 1 0 ( 1-11, 1-11) background rect[plot.background..rect.105]
## 2 1 ( 7- 7, 4- 4) panel-1-1 gTree[panel-1.gTree.17]
## 3 1 ( 7- 7, 8- 8) panel-2-1 gTree[panel-2.gTree.30]
## 4 3 ( 5- 5, 4- 4) axis-t-1-1 zeroGrob[NULL]
## 5 3 ( 5- 5, 8- 8) axis-t-2-1 zeroGrob[NULL]
## 6 3 ( 8- 8, 4- 4) axis-b-1-1 absoluteGrob[GRID.absoluteGrob.43]
## 7 3 ( 8- 8, 8- 8) axis-b-2-1 absoluteGrob[GRID.absoluteGrob.50]
## 8 3 ( 7- 7, 7- 7) axis-l-1-2 absoluteGrob[GRID.absoluteGrob.64]
## 9 3 ( 7- 7, 3- 3) axis-l-1-1 absoluteGrob[GRID.absoluteGrob.57]
## 10 3 ( 7- 7, 9- 9) axis-r-1-2 zeroGrob[NULL]
## 11 3 ( 7- 7, 5- 5) axis-r-1-1 zeroGrob[NULL]
## 12 2 ( 6- 6, 4- 4) strip-t-1-1 gtable[strip]
## 13 2 ( 6- 6, 8- 8) strip-t-2-1 gtable[strip]
## 14 4 ( 4- 4, 4- 8) xlab-t zeroGrob[NULL]
## 15 5 ( 9- 9, 4- 8) xlab-b titleGrob[axis.title.x..titleGrob.33]
## 16 6 ( 7- 7, 2- 2) ylab-l titleGrob[axis.title.y..titleGrob.36]
## 17 7 ( 7- 7,10-10) ylab-r zeroGrob[NULL]
## 18 8 ( 3- 3, 4- 8) subtitle zeroGrob[plot.subtitle..zeroGrob.102]
## 19 9 ( 2- 2, 4- 8) title zeroGrob[plot.title..zeroGrob.101]
## 20 10 (10-10, 4- 8) caption zeroGrob[plot.caption..zeroGrob.103]
Grobs are hierarchical objects and the general rules for traversing these structures fall into two categories:
- If a grob is of type
gtable
(as gt
above), accessing individual grobs that go into the table can be done through $grobs
.
- If a grob is NOT of type
gtable
, its children grobs can be accessed through $children
.
Looking at the gtable
above, we observe that grobs 6 and 7 correspond to the bottom axes of facets 1 and 2, respectively. Each of these axis grobs is of type absoluteGrob
, so using the two rules above, we can examine what they are made up of like this:
gt$grobs[[6]]$children
## (zeroGrob[axis.line.x..zeroGrob.40], gtable[axis])
## and likewise for gt$grobs[[7]]$children
Noting that the second child is a gtable
, we can continue descending the hierarchy of grobs until we arrive at gt$grobs[[6]]$children[[2]]$grobs[[2]]$children[[1]]
, which is a leaf of the grob hierarchy (its $children
is NULL
) and corresponds to the axis text. Let's examine its graphical parameters, which can be accessed through $gp
:
## Double-check that we have the correct text object
gt$grobs[[6]]$children[[2]]$grobs[[2]]$children[[1]]$label
## [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M"
## Display the summary of graphical parameters
str( gt$grobs[[6]]$children[[2]]$grobs[[2]]$children[[1]]$gp )
## List of 5
## $ fontsize : num 8.8
## $ col : chr [1:26] "black" "black" "black" "red" ...
## $ fontfamily: chr ""
## $ lineheight: num 0.9
## $ font : Named int 1
## ..- attr(*, "names")= chr "plain"
## - attr(*, "class")= chr "gpar"
Note that the col
attribute is of length 26 and corresponds exactly to the v
variable from the question. If we look at the bottom axis of the second facet (gt$grobs[[7]]$...
), we see that the same col
value is used there as well, leading to identical axis text coloring in both facets (as suggested in Z.Lin's solution).
Consequently, setting these color setting to only be the corresponding portions of v
"by hand" allows us to modify the original plot and achieve the desired result.
gt$grobs[[6]]$children[[2]]$grobs[[2]]$children[[1]]$gp$col <- v[1:13]
gt$grobs[[7]]$children[[2]]$grobs[[2]]$children[[1]]$gp$col <- v[14:26]
grid::grid.draw( gt )
X <- X %>% mutate(x.label = paste("<span style = 'color: ", ifelse( X$V1 %in% c( "D", "O", "T" ), "red", "black" ), ";'>", V1, "</span>", sep = ""), x.label = fct_reorder(x.label, as.character(V1))) ; ggplot( X, aes( x = x.label, y = V2 ) ) + geom_point() + theme( axis.text.x = element_markdown( hjust = 1)) + facet_wrap( ~V3, scales = "free" )
– Everyone