书稿纵轴题:正向竖排与单位外置

出版社 a–c、token 切词、单图/分面/翻转/热力四种布局

Published

October 28, 2026

经验来自《中国旱区农业技术发展报告2026》第2章、第5章 5.2 配图(拍板 2026-09-02)。可拷贝源:../R/ylab-stack.R、../R/theme-tech.R。交互终端 print(ggplot) 的字体/httpgd 问题见 另一篇笔记,与轴题几何无关。

出版社要求(a–c)

须同时满足:

  • a. 自上而下:从轴顶读到轴底。
  • b. 竖排:字形正向(angle = 0),用换行把题拆成一列。不是整串 axis.title.y 的 angle = -90(那是侧躺)。
  • c. 单位外置:末尾全角或半角括号((家)、(亿元)、(%))不进竖排串,放在该图或该分面纵轴正上方。

无量纲指标不编造单位。热力图左轴已是类别名,保持 y = NULL。

为何不用整串旋转或分面条

做法 为何不用
theme_tech 的 axis.title.y = -90 整串侧躺,不满足 b
右侧 facet_grid 条当轴题 轴值在左、题在右
switch = "y" + 换行 strip ggplot2 常把换行条裁掉
ggtext / 左侧 textGrob / 拼图 多依赖或多一层布局

切词:保护块优先于逐字

逐字 strsplit(s, "") 会把 RD资金投入 拆成 R / D。ylab-stack.R 用两个扁平函数,不包 ggplot:

  • split_ylab_unit(s):拆 title / unit;无括号则 unit = ""。
  • stack_ylab(s):先匹配保护块 ylab_keep(默认 R&D、RD、AI、GDP、DEA、CARS、CCR,长的优先),再整段拉丁词或数字,其余按字;paste(..., collapse = "\n")。

例如 RD资金投入(亿元) → 题竖排 RD / 资 / 金 / 投 / 入,单位 (亿元) 顶置。项目自有缩写在 source() 之后追加:ylab_keep <- c(ylab_keep, "NDVI")。

各图自己组单位标注的 tibble 或 annotate()。不要再写 add_stacked_ylab(p, ...) 这类包一层的 helper。

字号、字体与间距

轴题必须与 theme_tech 的 axis.text.y 同一套:family = simsun、size = 11(pt)。常量在 ylab-stack.R:ylab_text_family、ylab_text_size_pt、theme_ylab_stack。

问题 根因 做法
轴题压住刻度 geom_text(x = -Inf, hjust = 2.45) 的 hjust 以轴题自身宽度(约 1 汉字)为单位,挡不住 13000 单图改走 labs(y = stack_ylab(...)) + theme_ylab_stack(margin(r = ylab_title_gap_pt),由 ggplot 给轴题单独占位)
字号不一致 geom_text(size = 3.8) 默认单位是 mm;轴标签是 11pt 单图用 element_text(size = 11);分面 geom_text 写 size = ylab_text_size_pt, size.unit = "pt"

四种布局

单图(数值在左侧的柱/线)

  1. labs(y = stack_ylab(u$title))。
  2. 单位:geom_text / annotate,x = -Inf, y = Inf,hjust = 0.5, vjust = -1.15,11pt / simsun,size.unit = "pt"。
  3. coord_cartesian(clip = "off"),plot.margin 顶部留约 18pt。
  4. + theme_tech + theme_ylab_stack,顺序不能反,否则轴题被 -90 盖掉。

分面(每面题不同)

  1. strip.text = element_blank(),ylab(""),避免与 -90 叠字。
  2. 竖排题:geom_text,x = -Inf,y 取该面数据中位,hjust = ylab_stack_hjust,size.unit = "pt"。
  3. 单位:geom_text,y = Inf。
  4. clip = "off";左侧 plot.margin 用 ylab_stack_margin_l。

翻转图:coord_flip() 后数值轴在图底、已经横排;左侧是类别。底轴保持横排,不要把数值题搬到左边再竖排。

热力图:labs(y = NULL)。图例承担量纲说明,不在左侧再加竖排题。

theme_tech 放在绘图脚本里

对象在分析脚本里就画完:末尾 + theme_tech,再加本图覆盖(theme_ylab_stack、关 strip、axis.text.x 角度、margin)。qmd 只写 p <- plot_*,不在章文件里再叠主题。

theme_tech 的 axis.title.y = -90 保留,服务未改完的旧图。迁移旧项目时一脚本一提交,未轮到的脚本不要提前改;改完每个脚本单独 source 并 ggsave(..., device = ragg::agg_png, bg = "white") 目视(theme_tech 置空背景,不写 bg 会是透明底),不要为轴题整章 render。

验收

source("scripts/book-plot/R/ylab-stack.R")
u <- split_ylab_unit("RD资金投入(亿元)")
# title 为 RD资金投入,unit 为 (亿元)
tok <- strsplit(stack_ylab(u$title), "\n", fixed = TRUE)[[1]]
# tok[1] 必须是 RD,不能是 R

harness 内的自动断言见 ../test/Rscript/test-ylab-stack.R。

不要做

  • 多层嵌套 helper 改 plot。
  • 为无量纲图编造 (无量纲) 之类单位。
  • 翻转图左侧再竖排数值题。
  • 用 geom_text 默认 mm 字号写轴题。