Say I have a facetted ggplot like this:
data(iris)
ggplot(iris, aes(x = Petal.Width, y = Sepal.Length)) +
facet_grid(. ~ Species) +
geom_point()
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?
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)
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)
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)
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With