引言
相邻分面(facet)标签,文字长度太长(或太高),可能会互相堆叠。此时,可将分面文字标签拆开两行,分别放置。即,下图 minivan
的放置方式。
这是对一篇 Nature 文章中 Fig. 3 的临摹。
👉 Thresholds for adding degraded tropical forest to the conservation estate
后文,将展示 3 种实现方式。不是每种方式都足够完美,供参考。
本文代码没有充分注释,具体代码意图,可询问大语言模型。
包
library(dplyr)
library(ggplot2)
library(ggh4x)
library(ggtext)
library(patchwork)
数据
为了演示,故意把其中两个 class 的名称加长,
mpg2 <- mpg
mpg2$class <- ifelse(mpg2$class == "minivan", "minivan-MPV", mpg2$class)
mpg2$class <- ifelse(mpg2$class == "subcompact", "subcompact-B-segment", mpg2$class)
绘图:3 种实现方式
方法 1:效果不错
缺点是,需要手动调整图左侧边缘 margin 的宽度,手动设置标签 label 和线段 segment 的 X 坐标,
反复调试 X 坐标,直到合适为止。
# FONT <- "Myriad Pro"
FONT <- "Open Sans"
p0 <- ggplot(
data = mpg2,
aes(x = hwy, y = model)
) +
geom_point(size = 1) +
geom_text(
data = mpg2 |>
slice(1, .by = c(class, model)) |>
summarise(y.pos = n() / 2 + 0.5, .by = class) |>
filter(class %in% c("minivan-MPV", "subcompact-B-segment")),
aes(x = -23, y = y.pos, label = class),
size = 3.5,
color = "dodgerblue2",
hjust = 0.5,
angle = 90,
family = FONT
) +
geom_segment(
data = mpg2 |>
slice(1, .by = c(class, model)) |>
summarise(n.model = n(), .by = class) |>
mutate(
y.pos.max = n.model + 1,
y.pos.min = 0,
.by = class
) |>
filter(class %in% c("minivan-MPV", "subcompact-B-segment")),
aes(x = -21, xend = -21, y = y.pos.min, yend = y.pos.max),
size = 0.4,
color = "dodgerblue2"
) +
geom_text(
data = mpg2 |>
slice(1, .by = c(class, model)) |>
summarise(y.pos = n() / 2 + 0.5, .by = class) |>
filter(!class %in% c("minivan-MPV", "subcompact-B-segment")),
aes(x = -19, y = y.pos, label = class),
size = 3.5,
color = "green4",
hjust = 0.5,
angle = 90,
family = FONT
) +
geom_segment(
data = mpg2 |>
slice(1, .by = c(class, model)) |>
summarise(n.model = n(), .by = class) |>
mutate(
y.pos.max = n.model + 1,
y.pos.min = 0,
.by = class
) |>
filter(!class %in% c("minivan-MPV", "subcompact-B-segment")),
aes(x = -17, xend = -17, y = y.pos.min, yend = y.pos.max),
size = 0.4,
color = "green4"
) +
scale_x_continuous(
expand = expansion(mult = c(0, 0.06))
) +
facet_grid(
class ~ .,
scales = "free_y",
space = "free_y",
switch = "y"
) +
labs(
x = "Highway miles per gallon",
y = NULL
) +
coord_cartesian(
xlim = c(10, 50),
clip = "off"
) +
theme_classic(
base_family = FONT,
base_line_size = 0.3
) +
theme(
plot.margin = margin(20, 5, 5, 40),
strip.background = element_blank(),
strip.text = element_blank(),
strip.clip = "off",
strip.placement = "outside",
axis.title = element_text(size = 10),
axis.text = element_text(size = 10, color = "black"),
)
p0
# ggview::ggview(p0, width = 4.9, height = 7, dpi = 300)
ggsave(
"as-two-rows-facet-perfect.png",
p0,
width = 4.9,
height = 7,
dpi = 600
)
方法 2:通过两部分拼图实现,更为灵活
mpg2 <- mpg
mpg2$class <- ifelse(mpg2$class == "minivan", "minivan-MPV", mpg2$class)
mpg2$class <- ifelse(mpg2$class == "subcompact", "subcompact-B-segment", mpg2$class)
theme_set(
theme_classic(
base_family = FONT,
base_rect_size = 0.3,
base_line_size = 0.3
) +
theme(
strip.background = element_blank(),
strip.clip = "off",
strip.placement = "outside",
axis.title = element_text(size = 10),
axis.text = element_text(size = 10, color = "black"),
)
)
# Prepare p2a
p2a <- ggplot(
data = mpg2,
aes(x = hwy, y = model)
) +
geom_point(size = 1) +
scale_x_continuous(
expand = expansion(mult = c(0, 0.06))
) +
scale_y_discrete(
expand = expansion(add = c(0.5, 0.5))
) +
facet_grid(
class ~ .,
scales = "free_y",
space = "free_y",
switch = "y"
) +
labs(
x = "Highway miles per gallon",
y = NULL
) +
coord_cartesian(
xlim = c(10, 50),
clip = "off"
) +
theme(
strip.text = element_blank()
)
# ggview::ggview(
# p2a,
# width = 3,
# height = 7,
# dpi = 900
# )
# Prepare p2b
p2b <- ggplot(
data = mpg2,
aes(x = hwy, y = model)
) +
geom_blank() +
geom_text(
data = mpg2 |>
slice(1, .by = c(class, model)) |>
summarise(y.pos = n() / 2 + 0.5, .by = class) |>
filter(
class %in% c("minivan-MPV", "subcompact-B-segment")
),
aes(x = 17, y = y.pos, label = class),
size = 3.5,
color = "dodgerblue2",
hjust = 0.5,
angle = 90,
family = FONT,
inherit.aes = FALSE
) +
geom_segment(
data = mpg2 |>
slice(1, .by = c(class, model)) |>
summarise(n.model = n(), .by = class) |>
mutate(
y.pos.max = n.model + 0.5,
y.pos.min = 0.5,
.by = class
) |>
filter(
class %in% c("minivan-MPV", "subcompact-B-segment")
),
aes(x = 20, xend = 20, y = y.pos.min, yend = y.pos.max),
size = 0.4,
color = "dodgerblue2"
) +
geom_text(
data = mpg2 |>
slice(1, .by = c(class, model)) |>
summarise(y.pos = n() / 2 + 0.5, .by = class) |>
filter(
!class %in% c("minivan-MPV", "subcompact-B-segment")
),
aes(x = 24, y = y.pos, label = class),
size = 3.5,
color = "green4",
hjust = 0.5,
angle = 90,
family = FONT,
inherit.aes = FALSE
) +
geom_segment(
data = mpg2 |>
slice(1, .by = c(class, model)) |>
summarise(n.model = n(), .by = class) |>
mutate(
y.pos.max = n.model + 0.5,
y.pos.min = 0.5,
.by = class
) |>
filter(
!class %in% c("minivan-MPV", "subcompact-B-segment")
),
aes(x = 27, xend = 27, y = y.pos.min, yend = y.pos.max),
size = 0.4,
color = "green4"
) +
scale_x_continuous(
expand = expansion(mult = c(0, 0.06))
) +
scale_y_discrete(
expand = expansion(add = c(0.5, 0.5))
) +
facet_grid(
class ~ .,
scales = "free_y",
space = "free_y",
switch = "y"
) +
labs(
x = NULL,
y = NULL
) +
coord_cartesian(
xlim = c(17, 27),
clip = "off"
) +
theme(
panel.background = element_blank(),
strip.text = element_blank(),
axis.text = element_text(size = 0),
axis.line = element_blank(),
axis.ticks = element_blank()
)
# 拼图
(p2 <- p2b + p2a + plot_layout(widths = c(1, 5)))
# ggview::ggview(p2, width = 4.9, height = 7.5, dpi = 300)
ggsave(
"as-two-rows-plot-combine.png",
p2,
width = 4.9,
height = 7.5,
dpi = 600
)
方法 3:不完美,但富文本格式,可设置字体颜色
小技巧:#ffffff00 是透明色。
mpg2 <- mpg
mpg2$class <- ifelse(mpg2$class == "minivan", "minivan-MPV", mpg2$class)
mpg2$class <- ifelse(mpg2$class == "subcompact", "subcompact-B-segment", mpg2$class)
glue.color.string <- function(clr, str, cex = 10) {
glue::glue(
"<span style = 'color:{color}; font-size:{size}pt'>{text}</span>",
color = clr,
size = cex,
text = str
)
}
glue.color.string("red", "minivan-MPV")
<span style = 'color:red; font-size:10pt'>minivan-MPV</span>
class.levels <- ifelse(
mpg2$class %in% c("minivan-MPV", "subcompact-B-segment"),
glue.color.string("#ffffff00", mpg2$class),
glue.color.string("black", mpg2$class)
) |>
unique()
print(class.levels)
[1] "<span style = 'color:black; font-size:10pt'>compact</span>"
[2] "<span style = 'color:black; font-size:10pt'>midsize</span>"
[3] "<span style = 'color:black; font-size:10pt'>suv</span>"
[4] "<span style = 'color:black; font-size:10pt'>2seater</span>"
[5] "<span style = 'color:#ffffff00; font-size:10pt'>minivan-MPV</span>"
[6] "<span style = 'color:black; font-size:10pt'>pickup</span>"
[7] "<span style = 'color:#ffffff00; font-size:10pt'>subcompact-B-segment</span>"
优点是,富文本格式可设置各种颜色。
theme_set(
theme_classic(
base_family = FONT,
base_line_size = 0.3
) +
theme(
plot.margin = margin(30, 5, 5, 5),
strip.background = element_blank(),
strip.clip = "off",
strip.placement = "outside",
axis.title = element_text(size = 10),
axis.text = element_text(size = 10, color = "black"),
legend.position = c(1, 0.2),
legend.justification = c(1, 0),
legend.key.height = unit(6, "pt")
)
)
class.levels <- unique(mpg2$class)
class.colors <- RColorBrewer::brewer.pal(
n = length(class.levels),
name = "Dark2"
) |>
setNames(nm = class.levels)
mpg2$class.colored <- glue.color.string(class.colors[mpg2$class], mpg2$class)
mpg2$model.colored <- glue.color.string(class.colors[mpg2$class], mpg2$model)
p3 <- mpg2 |>
mutate(
class2 = ifelse(
class == "minivan-MPV",
class.colored,
glue.color.string("#ffffff00", class, cex = 0)
),
class3 = ifelse(
class == "subcompact-B-segment",
class.colored,
glue.color.string("#ffffff00", class, cex = 0)
),
class4 = ifelse(
class %in% c("minivan-MPV", "subcompact-B-segment"),
glue.color.string("#ffffff00", class, cex = 0),
class.colored
)
) |>
# View()
ggplot(
aes(x = hwy, y = model.colored)
) +
geom_point(size = 1) +
scale_x_continuous(
expand = expansion(mult = c(0, 0.06))
) +
facet_grid(
class2 + class4 + class3 ~ .,
scales = "free_y",
space = "free_y",
switch = "y"
) +
labs(
x = "Highway miles per gallon",
y = NULL
) +
coord_cartesian(
xlim = c(10, 50),
clip = "off"
) +
theme(
# strip.background = element_rect(color = "black", fill = NA),
strip.text = element_markdown(
size = 10,
fill = alpha("lightgrey", 0.2), # alpha 设置透明度
angle = 90,
padding = unit(2, "pt"),
r = unit(2, "pt")
),
axis.text.y = element_markdown(size = 10)
)
p3
# ggview::ggview(p3, width = 5, height = 7, dpi = 300)
ggsave(
"two-rows-facet-richtext.png",
p3,
width = 5,
height = 7,
dpi = 600
)