如何在调整ggplot绘图尺寸时保持文本的相对位置
问题场景
我用以下代码绘制了一幅ggplot图:
ggplot(mtcars, aes(x = mpg, y = wt)) + geom_point() + geom_hline(yintercept = 3) + theme(axis.title.x = element_text(vjust = 85))
可以看到X轴标题被下移到了水平直线的正下方。但当我调整绘图窗口尺寸时,问题出现了:
- 缩小尺寸(比如12.88521 x 11.08604 cm):X轴标题跑到了水平直线上方
- 放大尺寸(比如24.15646 x 20.32000 cm):X轴标题会跑到水平直线下方更远的位置
我试过用coord_fixed(),但看起来这个函数并不是用来解决这类问题的。
现在想请教:怎么才能让我用hjust/vjust调整文本位置时,不是基于绝对单位移动,而是相对于绘图区域的宽/高做相对移动,这样无论怎么调整绘图尺寸,文本都能保持在固定的相对位置?
解决方案
要实现文本位置的相对定位,核心思路是脱离vjust/hjust的绝对数值控制,改用基于绘图区域坐标或者相对比例的方式来定位文本,下面是几种实用的方法:
方法1:用annotation_custom()手动定位文本
annotation_custom()可以让你直接基于绘图区域的坐标(或者数据坐标)来放置元素,这样无论绘图尺寸怎么变,位置都会相对固定。比如你想把X轴标题放在水平直线(y=3)的下方,距离绘图底部一定比例的位置:
library(ggplot2) # 先绘制基础图形,隐藏默认X轴标题 p <- ggplot(mtcars, aes(x = mpg, y = wt)) + geom_point() + geom_hline(yintercept = 3) + theme(axis.title.x = element_blank()) # 添加自定义X轴标题,用npc相对坐标定位 p + annotation_custom( grob = textGrob("Miles/(US) gallon", hjust = 0.5), # 水平方向对齐X轴中点 xmin = mean(range(mtcars$mpg)), xmax = mean(range(mtcars$mpg)), # 垂直方向放在绘图区域从下往上10%的位置 ymin = unit(0.1, "npc"), ymax = unit(0.1, "npc") )
这里的npc是“normalized parent coordinates”的缩写,范围是0(底部/左侧)到1(顶部/右侧),所以用unit(0.1, "npc")就是把文本放在绘图区域从下往上10%的位置,水平居中对齐X轴中点,这样无论怎么调整绘图尺寸,位置都会相对固定。
方法2:用theme()中的margin结合相对单位
如果你还是想用axis.title.x来设置,那可以放弃vjust,改用margin参数,并且用相对单位(比如lines或者npc)来控制间距:
ggplot(mtcars, aes(x = mpg, y = wt)) + geom_point() + geom_hline(yintercept = 3) + theme( axis.title.x = element_text( hjust = 0.5, # 设置顶部外边距,用lines作为相对单位,数值可按需调整 margin = margin(t = 2, unit = "lines") ) )
这里的lines单位是相对于文本行高的,绘图尺寸变化时,间距会跟着文本和绘图的比例自动调整,不会出现位置突变的问题。
方法3:用ggtext包实现更灵活的定位(可选)
如果需要更复杂的排版,可以试试ggtext包的element_markdown(),它支持用CSS样式设置相对位置,但对于这个场景来说,前面两种方法已经足够实用。
为什么vjust会失效?
你之前遇到的问题,本质是vjust的数值是基于文本自身的边界框计算的,当绘图尺寸变化时,ggplot的内部坐标转换会让这个绝对数值对应的实际位置发生偏移。而用相对单位或者直接基于绘图区域坐标的方式,才能从根本上避免这个问题。
备注:内容来源于stack exchange,提问作者J. Doe

