Is it possible to change the alignment of only 1 facet title

You have the right idea but not the right spot. Basically you need to do something like this:

library('ggplot2')
library('grid')

data(iris)
pl <- ggplot(iris, aes(x = Petal.Width, y = Sepal.Length)) +
  facet_grid(. ~ Species) +
  geom_point()
gt <- ggplotGrob(pl)

gt$grobs[[13]]$grobs[[1]]$children$strip.text.x.top..titleGrob.186$children$GRID.text.184$x <- unit(0.1, 'npc')
grid.newpage()
grid.draw(gt)

enter image description here

But this won't work for you... it won't even work for me a second time since the titleGrob.xxx and GRID.text.xxx change each time you use gt <- ggplotGrob(pl)

So try a different method and knowing the location you need to edit as you have pointed out (it's in the first strip):

gp <- ggplotGrob(pl)
grid.ls(grid.force(gp))

# layout
#   background.1-13-13-1
#   panel-1-1.8-5-8-5
# ...
#   strip-t-1.7-5-7-5                        <- here
#     strip.1-1-1-1
#       strip.background.x..rect.533
#       strip.text.x.top..titleGrob.525
#         GRID.text.523
#   strip-t-2.7-7-7-7
#     strip.1-1-1-1
#       strip.background.x..rect.533
#       strip.text.x.top..titleGrob.528
#         GRID.text.526
#   strip-t-3.7-9-7-9
#     strip.1-1-1-1
#       strip.background.x..rect.533
#       strip.text.x.top..titleGrob.531
#         GRID.text.529
#   axis-t-1.6-5-6-5
# ...
#   title.3-9-3-5
#   caption.11-9-11-5
#   tag.2-2-2-2

You can use gPath to get the path without knowing the GRID.text.xxx in advance. Note that in your example, we can just edit the first GRID.text and it will work if global = FALSE, ie, if global = TRUE, all of them would change.

g1 <- editGrob(
  grid.force(gp),
  gPath('GRID.text'), grep = TRUE, global = FALSE,
  x = unit(0.25, 'npc')
)
grid.newpage()
grid.draw(g1)

enter image description here

However, you might need a very specific path, so follow the strip-t-1 down to your GRID.text (note that global = TRUE, and it only affects one strip)

g2 <- editGrob(
  grid.force(gp),
  gPath('strip-t-1', 'strip', 'strip', 'GRID.text'), grep = TRUE, global = TRUE,
  x = unit(0.75, 'npc')
)
grid.newpage()
grid.draw(g2)

enter image description here


This is something that eventually the ggtext package should be able to do in a general way, but as of now its HTML rendering code is too limited. A new rendering engine is in the works that supports much more CSS, including CSS selectors, which makes it possible to target formatting to specific data values in a generic way. It also supports the text-align property you need to left-align text.

Below are two examples of how this can work. Once the new rendering engine is integrated into ggtext you won't need the helper code anymore.

# packages needed
library(ggplot2)
library(dplyr)
library(glue)
library(rlang)
library(sinab)  # remotes::install_github("clauswilke/sinab")

# helper code that will eventually live in the ggtext package
element_html <- function(css = NULL, family = NULL, face = NULL, size = NULL, colour = NULL, fill = NULL,
                         linetype = NULL, linewidth = NULL, hjust = NULL, vjust = NULL, lineheight = NULL,
                         margin = NULL, width = NULL, height = NULL, color = NULL, 
                         debug = FALSE, inherit.blank = FALSE) {
  if (!is.null(color))
    colour <- color
  
  # doesn't work with other values at this time
  hjust <- 0
  vjust <- 1
  
  structure(
    list(
      css = css,
      family = family, face = face, size = size, colour = colour, fill = fill,
      linetype = linetype, linewidth = linewidth, hjust = hjust, vjust = vjust,
      lineheight = lineheight, margin = margin, width = width, height = height,
      debug = debug, inherit.blank = inherit.blank),
    class = c("element_html", "element_text", "element")
  )
}

element_grob.element_html <- function(element, label = "", x = NULL, y = NULL,
                                      family = NULL, face = NULL, colour = NULL, size = NULL,
                                      hjust = NULL, vjust = NULL, lineheight = NULL,
                                      margin = NULL, ...) {
  if (is.null(label))
    return(ggplot2::zeroGrob())
  
  # for now we ignore hjust and vjust, it doesn't work yet
  hj <- 0
  vj <- 1
  
  css <- element$css %||% ""
  
  html_grob(
    label, x = x, y = y, hjust = hj, vjust = vj,
    width = element$width, height = element$height,
    css = css
  )
}


# CSS styling
css <- '
p { text-align: center; padding-top: 2px; }
.setosa { text-align: left; }
'

# plotting code
iris %>%
  mutate(
    # set class attribute to species name
    facet_label = glue('<p class = "{Species}">{Species}</p>')
  ) %>%
  ggplot(aes(x = Petal.Width, y = Sepal.Length)) +
  facet_grid(. ~ facet_label) +
  geom_point() +
  theme(strip.text = element_html(css = css))

# another example with different styling
css <- '
p { text-align: center; padding-top: 2px; font-style: italic; }
.setosa { background-color: #0072B2; color: white; }
.versicolor { background-color: #E69F00; }
.virginica { background-color: #009E73;  color: white; }
'

# plotting code
iris %>%
  mutate(
    # set class attribute to species name
    facet_label = glue('<p class = "{Species}">I. {Species}</p>')
  ) %>%
  ggplot(aes(x = Petal.Width, y = Sepal.Length, color = Species)) +
  facet_grid(. ~ facet_label) +
  geom_point() +
  scale_color_manual(
    values = c(
      setosa = "#0072B2", versicolor = "#E69F00", virginica = "#009E73"
    ),
    guide = "none"
  ) +
  theme_bw() +
  theme(
    strip.background = element_blank(),
    strip.text = element_html(css = css)
  )

Created on 2020-09-16 by the reprex package (v0.3.0)