使用ggrepel覆盖水平定位 [英] Override horizontal positioning with ggrepel
问题描述
我正在处理类似于坡度图的图表,在该图表中,我想在标签的一侧或两侧放置标签,并留出足够的空白以使其在两侧都适合.在标签很长的情况下,我使用stringr::str_wrap
包裹了换行符.为了防止标签重叠,我将ggrepel::geom_text_repel
与direction = "y"
结合使用,因此x位置稳定,而y位置相互排斥.我还用hjust = "outward"
将左侧文本的右端对齐,反之亦然.
I'm working on a chart similar to a slopegraph, where I'd like to put labels along one or both sides with ample blank space to fit them on both sides. In cases where labels are very long, I've wrapped them using stringr::str_wrap
to place linebreaks. To keep labels from overlapping, I'm using ggrepel::geom_text_repel
with direction = "y"
so the x-positions are stable but the y-positions are repelled away from one another. I've also got hjust = "outward"
to align the left-side text at its right end and vice versa.
但是,似乎排斥位置将标签的边界框放置为hjust = "outward"
,但是该标签具有hjust = 0.5
的文本之内,即文本在其边界内居中.到现在为止,我还没有注意到这一点,但是在包装标签的情况下,第二行居中居中,而我希望这两行都左对齐或右对齐.
However, it seems that the repel positioning places the label's bounding box with an hjust = "outward"
, but the text within that label has hjust = 0.5
, i.e. text is centered within its bounds. Until now, I'd never noticed this, but with wrapped labels, the second line is awkwardly centered, whereas I'd expect to see both lines left-aligned or right-aligned.
这是一个基于mpg
数据集的示例.
Here's an example built off the mpg
dataset.
library(ggplot2)
library(dplyr)
library(ggrepel)
df <- structure(list(long_lbl = c("chevrolet, k1500 tahoe 4wd, auto(l4)",
"chevrolet, k1500 tahoe 4wd, auto(l4)", "subaru, forester awd, manual(m5)",
"subaru, forester awd, manual(m5)", "toyota, camry, manual(m5)",
"toyota, camry, manual(m5)", "toyota, toyota tacoma 4wd, manual(m5)",
"toyota, toyota tacoma 4wd, manual(m5)", "volkswagen, jetta, manual(m5)",
"volkswagen, jetta, manual(m5)"), year = c(1999L, 2008L, 1999L,
2008L, 1999L, 2008L, 1999L, 2008L, 1999L, 2008L), mean_cty = c(11,
14, 18, 20, 21, 21, 15, 17, 33, 21)), class = c("tbl_df", "tbl",
"data.frame"), row.names = c(NA, -10L))
df_wrap <- df %>%
mutate(wrap_lbl = stringr::str_wrap(long_lbl, width = 25))
ggplot(df_wrap, aes(x = year, y = mean_cty, group = long_lbl)) +
geom_line() +
geom_text_repel(aes(label = wrap_lbl),
direction = "y", hjust = "outward", seed = 57, min.segment.length = 100) +
scale_x_continuous(expand = expand_scale(add = 10))
hjust
的其他值也会发生相同的情况.查看该函数的源,我看到一条线指出了这个问题:
The same thing happens with other values of hjust
. Looking at the function's source, I see a line that points to this issue:
hjust = x$data$hjust %||% 0.5,
其中,如果x$data$hjust
为null,则%||%
分配0.5.据我所知,但是看来我设置的hjust
并没有延续到这个位置,而是变成了空值.
where %||%
assigns 0.5 if x$data$hjust
is null. That's as far as I understand, but it seems that the hjust
I've set isn't being carried over to this positioning and is instead coming up null.
我错过了什么吗?任何人都可以看到我可以在不重新实现整个算法的情况下覆盖它的地方吗?还是这里有一个错误使我的hjust
掉了?
Have I missed something? Can anyone see where I might override this without reimplementing the whole algorithm? Or is there a bug here that drops my hjust
?
推荐答案
TL; DR:可能是错误
TL;DR: probably a bug
长答案:
我认为这可能是代码中的错误.我检查了所绘制图的gtable,其中hjust
是通过数字正确指定的:
I think it might be a bug in the code. I checked the gtable of the plot you made, wherein the hjust
was specified numerically and correctly:
# Assume 'g' is the plot saved under the variable 'g'
gt <- ggplotGrob(g)
# Your number at the end of the geom may vary
textgrob <- gt$grobs[[6]]$children$geom_text_repel.textrepeltree.1578
head(textgrob$data$hjust)
[1] 1 0 1 0 1 0
让我想到的是(1)无法通过在gtable中弄乱来固定绘图,并且(2)textrepeltree
类grob的绘制时间代码可能包含一些错误.这是有道理的,因为在调整绘图设备的大小时会重新放置标签.因此,当我们查看您提供的链接中的makeContent.textrepeltree()
代码时,我们可以看到hjust
参数被传递给makeTextRepelGrobs()
.让我们看一下相关的形式:
Which got me thinking that (1) the plot can't be fixed by messing around in the gtable and (2) the drawtime code for the textrepeltree
class of grobs may contain some errors. This makes sense, since the labels are repositioned when the plot device is resized. So when we look at the makeContent.textrepeltree()
code in the link you provided, we can see that the hjust
parameter is passed on to makeTextRepelGrobs()
. Let's have a look at the relevant formals:
makeTextRepelGrobs <- function(
...other_arguments...,
just = "center",
...other_arguments...,
hjust = 0.5,
vjust = 0.5
) { ...body...}
我们可以看到hjust
是有效的参数,但是还存在一个just
参数,该参数不是从makeContent.textrepeltree()
传递过来的.
We can see that hjust
is a valid argument, but there also exists a just
argument, which is an argument that is not passed on from makeContent.textrepeltree()
.
当我们查看函数体时,有以下两行:
When we look at the function body there are these two lines:
hj <- resolveHJust(just, NULL)
vj <- resolveVJust(just, NULL)
其中resolveH/VJust
是从网格包中导入的. resolveHJust()
本质上检查第二个参数是否为NULL
,如果为true,则默认为第一个参数,否则返回第二个参数.您会看到传递给makeTextRepelGrobs()
的hjust
未被传递给resolveHJust()
,这似乎是hjust
参数意外删除的地方.
Where resolveH/VJust
are imported from the grid package. The resolveHJust()
essentially checks whether the second argument is NULL
and if that is true, default to the first argument, otherwise return the second argument. You can see that the hjust
that was passed on to makeTextRepelGrobs()
does not get passed to resolveHJust()
, and this seems to be the point where your hjust
parameter is dropped unexpectedly.
接下来的代码是制作实际文本杂物的地方:
Further down the code is where the actual text grobs are made:
t <- textGrob(
...other_arguments...
just = c(hj, vj),
...other_arguments...
)
我认为解决方法相对简单:您只需提供hjust
作为resolveHJust()
的第二个参数即可.但是,由于makeTextRepelGrobs()
是ggrepel的内部文件,并且不会导出,因此您必须复制很多额外的代码才能使它工作. (不确定是否仅复制makeTextRepelGrob()
就足够了,还没有进行测试)
I imagine that the fix would be relatively straightforward: you would just have to supply hjust
as the second argument to resolveHJust()
. However, since that makeTextRepelGrobs()
is internal to ggrepel and does not get exported, you would have to copy a lot of extra code to get this to work. (Not sure if only copying the makeTextRepelGrob()
would be sufficient, haven't tested this)
所有这些使我得出的结论是,在geom_text_repel()
中指定的hjust
在绘制时间的最后时刻被makeTextRepelGrobs()
内部函数丢失了.
All of this leaves me to conclude that the hjust
that you specified in geom_text_repel()
gets lost at the last moment of drawtime by the makeTextRepelGrobs()
internal function.
这篇关于使用ggrepel覆盖水平定位的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!