I seem unable to correctly color axis text on a faceted plot when the scales
parameter is set to "free"
. Consider the following dataset:
当scales参数设置为“free”时,我似乎无法在刻面图上正确地着色轴文本。请考虑以下数据集:
library( ggplot2 )
X <- data.frame( V1 = LETTERS, V2 = runif( 26 ),
V3 = rep( c("F1", "F2"), each = 13 ) )
We can plot the data on a single facet, highlighting the letters D, O, T as follows:
我们可以在单个面上绘制数据,突出显示字母D,O,T如下:
v <- ifelse( X$V1 %in% c( "D", "O", "T" ), "red", "black" )
g <- ggplot( X, aes( x = V1, y = V2 ) ) + geom_point() +
theme( axis.text.x = element_text( color = v ) )
Making the plot faceted using the default scales = "fixed"
correctly highlights D, O, T on both facets.
使用默认的scale =“fixed”正确地绘制切面图会突出显示两个面上的D,O,T。
g + facet_wrap( ~V3 )
However, switching the scales
parameter to "free"
leads to unexpected behavior, where only D and Q are highlighted.
但是,将scale参数切换为“free”会导致意外行为,其中仅突出显示D和Q.
g + facet_wrap( ~V3, scales = "free" )
My question: is this a bug or do I need to somehow modify my definition of v
to account for free scales. If it is a bug, does anybody know of a workaround to highlight specific axis text in each (free-scaled) facet?
我的问题:这是一个错误还是我需要以某种方式修改我对v的定义来解释*规模。如果它是一个错误,是否有人知道一个解决方法,以突出显示每个(*缩放)方面的特定轴文本?
EDIT: Own answer moved to answers, as suggested by Henrik.
编辑:Henrik建议,自己的答案转移到答案。
2 个解决方案
#1
5
I don't think it's a bug. The problem is that v
here is basically a string of characters, length 26, which defines colours for the first 26 breaks on the x-axis. When the x-axis has 26 breaks exactly, well & good; when it has less than that (which is the case when you set scales="free"
), it simply restarts at the beginning for each axis. Q is red here because it's in the fourth position in the second plot, although the v[4]
's red was meant for D, in the first plot.
我不认为这是一个错误。问题是这里的v基本上是一串字符,长度为26,它定义了x轴上前26个中断的颜色。当x轴完全有26次断裂时,良好且良好;当它小于那个(当你设置scale =“free”时就是这种情况),它只是在每个轴的开头重新开始。 Q在这里是红色的,因为它在第二个图中处于第四个位置,尽管在第一个图中v [4]的红色表示D。
Based on what I've tried & read here on SO, one can't map aesthetics into theme()
, which controls the appearance of axis text in ggplot.
根据我在SO上尝试和阅读的内容,我们无法将美学映射到theme(),它控制ggplot中轴文本的外观。
It's possible to hack a solution by hiding the axis & using geom_text()
instead to simulate an axis, since the latter does accept aesthetics mapped from the data. It may not be very elegant, though:
可以通过隐藏轴并使用geom_text()代替模拟轴来破解解决方案,因为后者确实接受从数据映射的美学。但它可能不是很优雅:
g2 <- ggplot(cbind(X, v), #add v to X
aes(x = V1, y = V2)) +
geom_point() +
# make space to accommodate the fake axis
expand_limits(y = -0.05) +
# create a strip of white background under the fake axis
geom_rect(ymin = -5, ymax = 0, xmin = 0, xmax = nrow(X) + 1, fill = "white") +
# fake axis layer, aligned below y = 0
geom_text(aes(colour = v, label = V1), y = 0, vjust = 1.1) +
# specify the font colours for fake axis
scale_colour_manual(values = c("black", "red"), guide = F) +
# hide the actual x-axis text / ticks
theme(axis.text.x = element_blank(), axis.ticks.x = element_blank())
g2 + facet_wrap( ~V3, scales = "free" )
#2
3
After digging through the graphical objects (grobs) associated with the plot, I came across a potential hack to get around the issue. While not as elegant as Z.Lin's solution, I wanted to share it for educational purposes.
在挖掘了与绘图相关的图形对象(grobs)之后,我遇到了一个潜在的黑客来解决这个问题。虽然不如Z.Lin的解决方案那么优雅,但我想分享它用于教育目的。
We begin by retrieving grobs with
我们首先检索grobs
gt <- ggplotGrob( g + facet_wrap( ~V3, scales = "free" ) )
## TableGrob (11 x 11) "layout": 20 grobs
## z cells name grob
## 1 0 ( 1-11, 1-11) background rect[plot.background..rect.105]
## 2 1 ( 7- 7, 4- 4) panel-1-1 gTree[panel-1.gTree.17]
## 3 1 ( 7- 7, 8- 8) panel-2-1 gTree[panel-2.gTree.30]
## 4 3 ( 5- 5, 4- 4) axis-t-1-1 zeroGrob[NULL]
## 5 3 ( 5- 5, 8- 8) axis-t-2-1 zeroGrob[NULL]
## 6 3 ( 8- 8, 4- 4) axis-b-1-1 absoluteGrob[GRID.absoluteGrob.43]
## 7 3 ( 8- 8, 8- 8) axis-b-2-1 absoluteGrob[GRID.absoluteGrob.50]
## 8 3 ( 7- 7, 7- 7) axis-l-1-2 absoluteGrob[GRID.absoluteGrob.64]
## 9 3 ( 7- 7, 3- 3) axis-l-1-1 absoluteGrob[GRID.absoluteGrob.57]
## 10 3 ( 7- 7, 9- 9) axis-r-1-2 zeroGrob[NULL]
## 11 3 ( 7- 7, 5- 5) axis-r-1-1 zeroGrob[NULL]
## 12 2 ( 6- 6, 4- 4) strip-t-1-1 gtable[strip]
## 13 2 ( 6- 6, 8- 8) strip-t-2-1 gtable[strip]
## 14 4 ( 4- 4, 4- 8) xlab-t zeroGrob[NULL]
## 15 5 ( 9- 9, 4- 8) xlab-b titleGrob[axis.title.x..titleGrob.33]
## 16 6 ( 7- 7, 2- 2) ylab-l titleGrob[axis.title.y..titleGrob.36]
## 17 7 ( 7- 7,10-10) ylab-r zeroGrob[NULL]
## 18 8 ( 3- 3, 4- 8) subtitle zeroGrob[plot.subtitle..zeroGrob.102]
## 19 9 ( 2- 2, 4- 8) title zeroGrob[plot.title..zeroGrob.101]
## 20 10 (10-10, 4- 8) caption zeroGrob[plot.caption..zeroGrob.103]
Grobs are hierarchical objects and the general rules for traversing these structures fall into two categories:
Grob是分层对象,遍历这些结构的一般规则分为两类:
- If a grob is of type
gtable
(asgt
above), accessing individual grobs that go into the table can be done through$grobs
. - If a grob is NOT of type
gtable
, its children grobs can be accessed through$children
.
如果grob是gtable类型(如上所述),访问进入表的各个grob可以通过$ grobs完成。
如果grob不是gtable类型,它的子窗口可以通过$ children访问。
Looking at the gtable
above, we observe that grobs 6 and 7 correspond to the bottom axes of facets 1 and 2, respectively. Each of these axis grobs is of type absoluteGrob
, so using the two rules above, we can examine what they are made up of like this:
观察上面的gtable,我们观察到凹槽6和7分别对应于刻面1和2的底轴。这些轴grob中的每一个都是absoluteGrob类型,所以使用上面的两个规则,我们可以检查它们是由什么构成的:
gt$grobs[[6]]$children
## (zeroGrob[axis.line.x..zeroGrob.40], gtable[axis])
## and likewise for gt$grobs[[7]]$children
Noting that the second child is a gtable
, we can continue descending the hierarchy of grobs until we arrive at gt$grobs[[6]]$children[[2]]$grobs[[2]]$children[[1]]
, which is a leaf of the grob hierarchy (its $children
is NULL
) and corresponds to the axis text. Let's examine its graphical parameters, which can be accessed through $gp
:
注意到第二个孩子是gtable,我们可以继续降低grobs的层次结构,直到我们到达gt $ grobs [[6]] $ children [[2]] $ grobs [[2]] $ children [[1]] ,它是grob层次结构的一个叶子(它的$ children为NULL)并且对应于轴文本。让我们检查一下它的图形参数,可以通过$ gp访问它们:
## Double-check that we have the correct text object
gt$grobs[[6]]$children[[2]]$grobs[[2]]$children[[1]]$label
## [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M"
## Display the summary of graphical parameters
str( gt$grobs[[6]]$children[[2]]$grobs[[2]]$children[[1]]$gp )
## List of 5
## $ fontsize : num 8.8
## $ col : chr [1:26] "black" "black" "black" "red" ...
## $ fontfamily: chr ""
## $ lineheight: num 0.9
## $ font : Named int 1
## ..- attr(*, "names")= chr "plain"
## - attr(*, "class")= chr "gpar"
Note that the col
attribute is of length 26 and corresponds exactly to the v
variable from the question. If we look at the bottom axis of the second facet (gt$grobs[[7]]$...
), we see that the same col
value is used there as well, leading to identical axis text coloring in both facets (as suggested in Z.Lin's solution).
请注意,col属性的长度为26,并且与问题中的v变量完全对应。如果我们查看第二个方面的底部轴(gt $ grobs [[7]] $ ...),我们会看到在那里使用相同的col值,导致两个方面的相同轴文本着色(如在Z.Lin的解决方案中提出)。
Consequently, setting these color setting to only be the corresponding portions of v
"by hand" allows us to modify the original plot and achieve the desired result.
因此,将这些颜色设置仅设置为v“手动”的相应部分允许我们修改原始图并实现期望的结果。
gt$grobs[[6]]$children[[2]]$grobs[[2]]$children[[1]]$gp$col <- v[1:13]
gt$grobs[[7]]$children[[2]]$grobs[[2]]$children[[1]]$gp$col <- v[14:26]
grid::grid.draw( gt )
#1
5
I don't think it's a bug. The problem is that v
here is basically a string of characters, length 26, which defines colours for the first 26 breaks on the x-axis. When the x-axis has 26 breaks exactly, well & good; when it has less than that (which is the case when you set scales="free"
), it simply restarts at the beginning for each axis. Q is red here because it's in the fourth position in the second plot, although the v[4]
's red was meant for D, in the first plot.
我不认为这是一个错误。问题是这里的v基本上是一串字符,长度为26,它定义了x轴上前26个中断的颜色。当x轴完全有26次断裂时,良好且良好;当它小于那个(当你设置scale =“free”时就是这种情况),它只是在每个轴的开头重新开始。 Q在这里是红色的,因为它在第二个图中处于第四个位置,尽管在第一个图中v [4]的红色表示D。
Based on what I've tried & read here on SO, one can't map aesthetics into theme()
, which controls the appearance of axis text in ggplot.
根据我在SO上尝试和阅读的内容,我们无法将美学映射到theme(),它控制ggplot中轴文本的外观。
It's possible to hack a solution by hiding the axis & using geom_text()
instead to simulate an axis, since the latter does accept aesthetics mapped from the data. It may not be very elegant, though:
可以通过隐藏轴并使用geom_text()代替模拟轴来破解解决方案,因为后者确实接受从数据映射的美学。但它可能不是很优雅:
g2 <- ggplot(cbind(X, v), #add v to X
aes(x = V1, y = V2)) +
geom_point() +
# make space to accommodate the fake axis
expand_limits(y = -0.05) +
# create a strip of white background under the fake axis
geom_rect(ymin = -5, ymax = 0, xmin = 0, xmax = nrow(X) + 1, fill = "white") +
# fake axis layer, aligned below y = 0
geom_text(aes(colour = v, label = V1), y = 0, vjust = 1.1) +
# specify the font colours for fake axis
scale_colour_manual(values = c("black", "red"), guide = F) +
# hide the actual x-axis text / ticks
theme(axis.text.x = element_blank(), axis.ticks.x = element_blank())
g2 + facet_wrap( ~V3, scales = "free" )
#2
3
After digging through the graphical objects (grobs) associated with the plot, I came across a potential hack to get around the issue. While not as elegant as Z.Lin's solution, I wanted to share it for educational purposes.
在挖掘了与绘图相关的图形对象(grobs)之后,我遇到了一个潜在的黑客来解决这个问题。虽然不如Z.Lin的解决方案那么优雅,但我想分享它用于教育目的。
We begin by retrieving grobs with
我们首先检索grobs
gt <- ggplotGrob( g + facet_wrap( ~V3, scales = "free" ) )
## TableGrob (11 x 11) "layout": 20 grobs
## z cells name grob
## 1 0 ( 1-11, 1-11) background rect[plot.background..rect.105]
## 2 1 ( 7- 7, 4- 4) panel-1-1 gTree[panel-1.gTree.17]
## 3 1 ( 7- 7, 8- 8) panel-2-1 gTree[panel-2.gTree.30]
## 4 3 ( 5- 5, 4- 4) axis-t-1-1 zeroGrob[NULL]
## 5 3 ( 5- 5, 8- 8) axis-t-2-1 zeroGrob[NULL]
## 6 3 ( 8- 8, 4- 4) axis-b-1-1 absoluteGrob[GRID.absoluteGrob.43]
## 7 3 ( 8- 8, 8- 8) axis-b-2-1 absoluteGrob[GRID.absoluteGrob.50]
## 8 3 ( 7- 7, 7- 7) axis-l-1-2 absoluteGrob[GRID.absoluteGrob.64]
## 9 3 ( 7- 7, 3- 3) axis-l-1-1 absoluteGrob[GRID.absoluteGrob.57]
## 10 3 ( 7- 7, 9- 9) axis-r-1-2 zeroGrob[NULL]
## 11 3 ( 7- 7, 5- 5) axis-r-1-1 zeroGrob[NULL]
## 12 2 ( 6- 6, 4- 4) strip-t-1-1 gtable[strip]
## 13 2 ( 6- 6, 8- 8) strip-t-2-1 gtable[strip]
## 14 4 ( 4- 4, 4- 8) xlab-t zeroGrob[NULL]
## 15 5 ( 9- 9, 4- 8) xlab-b titleGrob[axis.title.x..titleGrob.33]
## 16 6 ( 7- 7, 2- 2) ylab-l titleGrob[axis.title.y..titleGrob.36]
## 17 7 ( 7- 7,10-10) ylab-r zeroGrob[NULL]
## 18 8 ( 3- 3, 4- 8) subtitle zeroGrob[plot.subtitle..zeroGrob.102]
## 19 9 ( 2- 2, 4- 8) title zeroGrob[plot.title..zeroGrob.101]
## 20 10 (10-10, 4- 8) caption zeroGrob[plot.caption..zeroGrob.103]
Grobs are hierarchical objects and the general rules for traversing these structures fall into two categories:
Grob是分层对象,遍历这些结构的一般规则分为两类:
- If a grob is of type
gtable
(asgt
above), accessing individual grobs that go into the table can be done through$grobs
. - If a grob is NOT of type
gtable
, its children grobs can be accessed through$children
.
如果grob是gtable类型(如上所述),访问进入表的各个grob可以通过$ grobs完成。
如果grob不是gtable类型,它的子窗口可以通过$ children访问。
Looking at the gtable
above, we observe that grobs 6 and 7 correspond to the bottom axes of facets 1 and 2, respectively. Each of these axis grobs is of type absoluteGrob
, so using the two rules above, we can examine what they are made up of like this:
观察上面的gtable,我们观察到凹槽6和7分别对应于刻面1和2的底轴。这些轴grob中的每一个都是absoluteGrob类型,所以使用上面的两个规则,我们可以检查它们是由什么构成的:
gt$grobs[[6]]$children
## (zeroGrob[axis.line.x..zeroGrob.40], gtable[axis])
## and likewise for gt$grobs[[7]]$children
Noting that the second child is a gtable
, we can continue descending the hierarchy of grobs until we arrive at gt$grobs[[6]]$children[[2]]$grobs[[2]]$children[[1]]
, which is a leaf of the grob hierarchy (its $children
is NULL
) and corresponds to the axis text. Let's examine its graphical parameters, which can be accessed through $gp
:
注意到第二个孩子是gtable,我们可以继续降低grobs的层次结构,直到我们到达gt $ grobs [[6]] $ children [[2]] $ grobs [[2]] $ children [[1]] ,它是grob层次结构的一个叶子(它的$ children为NULL)并且对应于轴文本。让我们检查一下它的图形参数,可以通过$ gp访问它们:
## Double-check that we have the correct text object
gt$grobs[[6]]$children[[2]]$grobs[[2]]$children[[1]]$label
## [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M"
## Display the summary of graphical parameters
str( gt$grobs[[6]]$children[[2]]$grobs[[2]]$children[[1]]$gp )
## List of 5
## $ fontsize : num 8.8
## $ col : chr [1:26] "black" "black" "black" "red" ...
## $ fontfamily: chr ""
## $ lineheight: num 0.9
## $ font : Named int 1
## ..- attr(*, "names")= chr "plain"
## - attr(*, "class")= chr "gpar"
Note that the col
attribute is of length 26 and corresponds exactly to the v
variable from the question. If we look at the bottom axis of the second facet (gt$grobs[[7]]$...
), we see that the same col
value is used there as well, leading to identical axis text coloring in both facets (as suggested in Z.Lin's solution).
请注意,col属性的长度为26,并且与问题中的v变量完全对应。如果我们查看第二个方面的底部轴(gt $ grobs [[7]] $ ...),我们会看到在那里使用相同的col值,导致两个方面的相同轴文本着色(如在Z.Lin的解决方案中提出)。
Consequently, setting these color setting to only be the corresponding portions of v
"by hand" allows us to modify the original plot and achieve the desired result.
因此,将这些颜色设置仅设置为v“手动”的相应部分允许我们修改原始图并实现期望的结果。
gt$grobs[[6]]$children[[2]]$grobs[[2]]$children[[1]]$gp$col <- v[1:13]
gt$grobs[[7]]$children[[2]]$grobs[[2]]$children[[1]]$gp$col <- v[14:26]
grid::grid.draw( gt )