Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Say I have a facetted ggplot like this:

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

enter image description here

Question:

Is it possible to align only the 1st facet label ("setosa") to the left, but keep the others central?

I have not had any luck with straight ggplot, so I tried converting to a gtable (code below), and I can see that the element I probably need to modify is tableGrb #13 - strip-t-1, but I'm not sure how to do so. I tried changing the gt$layout$l and gt$layout$r values to 4 (was 5) but that moves the heading off the facet entirely.

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

gt$layout[gt$layout$name == 'strip-t-1', c('l', 'r')] <- c(4, 4) # my attempt
grid.newpage()
grid.draw(gt)

Does anyone know if this is possible?

like image 635
morgan121 Avatar asked Sep 16 '20 03:09

morgan121


2 Answers

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

like image 51
rawr Avatar answered Oct 08 '22 04:10

rawr


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)

like image 23
Claus Wilke Avatar answered Oct 08 '22 05:10

Claus Wilke