I have some generic plot with continuous X and categorical Y axis, for example:
data(iris)
p1 <-
ggplot(iris, aes(y=Species, x=Sepal.Length))
geom_violin()
Now I want to annotate it with some labels that should be positioned right next to the Y axis, with a quick-and-dirty solution like this:
df.labels <- data.frame(Species=c('setosa','versicolor','virginica'), Labels=c('labA','labB','labC'))
p2 <- p1 geom_text(data = df.labels, aes(x=4, y=Species, label=Labels))
Note that I had to indicate x=4
explicitly; for more general case I made this function:
add_label <- function(inpPlot, inpData, colLabel){
plotX1 <- ggplot_build(inpPlot)$layout$panel_params[[1]]$x.range[1]
plotX2 <- ggplot_build(inpPlot)$layout$panel_params[[1]]$x.range[2]
plotW <- plotX2 - plotX1
posX <- plotX1 - 0.05*plotW
colGrp <- as_label(inpPlot$mapping$y)
inpPlot
coord_cartesian(clip='off')
geom_text(data = inpData, aes_string(x='posX', y=colGrp, label=colLabel))
}
p3 <- add_label(p1, df.labels, 'Labels')
What I don't like in this solution:
- Offset calculated as "5% of plot width" doesn't work very well when I resize plot window (I would like to have a fixed absolute distance between the axis and the labels)
- It doesn't work if my initial plot has
coord_cartesian()
applied, for example:
p1c <- p1 coord_cartesian(xlim = c(5,NA))
p4 <- add_label(p1c, df.labels, 'Labels')
Labels are shown on the plot area itself, I want them to be outside of the plot area (but "inside" from the Y axis, i.e. to the right from the axis labels).
What I would like to have ideally, is something like this:
p5 <- add_label_good(p1c, df.labels, 'Labels')
i.e. it should draw labels between the axis and plot area, and this space should be of fixed width.
Is there any way at all to achieve this?
CodePudding user response:
You might consider patchwork
, with two aligned plots:
library(patchwork)
plot_labels <- ggplot(df.labels, aes(x = 0, y = Species, label = Labels))
geom_text(hjust = 0)
theme_void()
theme(axis.text.y = element_text(),
axis.title.y = element_text(angle = 90, margin = margin(0,12,0,0, unit = "pt")))
plot_labels
(p1 theme(axis.title.y = element_blank(),
axis.text.y = element_blank()))
plot_layout(widths = c(1,8))
CodePudding user response:
One option is to use facet strips:
p1 facet_grid(Species~., scales = 'free_y', switch = "y",
labeller = labeller(Species = function(x) {
df.labels$Labels[match(x, df.labels$Species)]}))
theme(text = element_text(size = 16),
strip.text.y.left = element_text(angle = 0, face = "bold",
margin = margin(10, 10, 10, 10)),
strip.background = element_blank(),
panel.spacing = unit(0, "mm"))
Another option is to use ggtext::element_markdown()
and use the axis text itself as a label:
p1
scale_y_discrete(labels = function(x) {
paste(x, df.labels$Labels[match(x, df.labels$Species)],
sep = paste0(rep(" ", 5), "<b>", collapse = ""))
})
theme(text = element_text(size = 16),
axis.text.y = ggtext::element_markdown())
For a truly fixed-width text area on the far left of the plot, you could use annotation_custom
p1
annotation_custom(grid::rectGrob(x = unit(10, "mm"), y = 0.5,
width = unit(20, "mm"), height = 1,
gp = grid::gpar(col = NA)))
annotation_custom(grid::textGrob(df.labels$Labels,
x = unit(10, "mm"),
y = c(0.81, 0.5, 0.19)))