如何在ggplot中为不同geom设置独立颜色比例尺并显示双图例
在ggplot中为两个geom_segment实现独立颜色比例尺的方案
问题描述
我无法在同一张ggplot图中为两个不同的geom_segment实现两种独立的颜色比例尺。尝试了三种方法均无效:
- 手动定义两个
scale_colour_manual,但第二个会覆盖第一个的设置 - 将第二个颜色比例尺定义为变量,没有效果
- 使用
fill参数搭配第二个比例尺,同样无效
需要找到正确的实现方式,同时让图表显示两个独立图例,分别对应两个geom_segment。
原代码与数据
原绘图代码
ggplot(data = df2, #%>% #filter(MM == "aoi"), aes(x = start_pm, xend = end_pm, y = MM_by, yend = MM_by, color = MM_annotation)) + geom_segment(size = 3) + geom_point(aes(x = max_x, y = MM_by), alpha = 0) + #plot invisible dummy end points scale_y_discrete(name = NULL, limits = rev) + #rev to get A at the top facet_wrap(~ Utterance, scales = "free_x", ncol = 1) + scale_colour_manual(values = c("*" = "lemonchiffon", "A" = "darkorange", "B" = "lawngreen", "C" = "slateblue1")) + # add vertical lines demarcating word end points: geom_vline(data = df2 %>% filter(MM == "word"), aes(xintercept = end_pm), lty = 3)+ # add words: geom_text(data = df2 %>% filter(MM == "word"), aes(x = end_pm -(end_pm - start_pm)/2, label = MM_annotation), size = 3, colour = "white")+ # gesture: geom_segment(data = df2 %>% filter(MM == "gesture") #%>% #mutate(col = ifelse(MM_annotation == "prep", "skyblue", # ifelse(MM_annotation == "stroke", "royalblue", # ifelse(MM_annotation == "hold", "blue", "navyblue"))) , aes(x = start_pm, xend = end_pm, y = MM_by, yend = MM_by, color = MM_annotation)) + scale_colour_manual(values = c("prep" = "lightblue", "stroke" = "royalblue", "hold" = "blue", "retract" = "navyblue"))
数据集
df2 <- structure(list(Utterance = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 3L, 3L, 3L, 3L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L), levels = c("C: [what's] a !mountain! for you: (#1)", "NA: (0.554) (#1)", "C: ((v: laughs))= (#1)", "A: =I don't know the (Schauinsland) is a small mount[ain] (#1)" ), class = "factor"), MM_by = c("Gaze_by_A", "Gaze_by_A", "Gaze_by_A", "Gaze_by_A", "Gaze_by_B", "Gaze_by_B", "Gaze_by_B", "Gaze_by_B", "Gaze_by_B", "Gaze_by_C", "Words_by_C", "Words_by_C", "Words_by_C", "Words_by_C", "Words_by_C", "Words_by_C", "Gesture_by_C", "Gesture_by_C", "Gesture_by_C", "Gaze_by_A", "Gaze_by_A", "Gaze_by_B", "Gaze_by_C", "Gesture_by_C", "Gesture_by_C", "Gaze_by_A", "Gaze_by_B", "Gaze_by_C", "Gesture_by_C", "Gaze_by_A", "Gaze_by_A", "Gaze_by_B", "Gaze_by_B", "Gaze_by_C", "Gaze_by_C", "Words_by_A", "Words_by_A", "Words_by_A", "Words_by_A", "Words_by_A", "Words_by_A", "Words_by_A", "Words_by_A", "Words_by_A", "Words_by_A", "Gesture_by_A", "Gesture_by_A"), MM = c("aoi", "aoi", "aoi", "aoi", "aoi", "aoi", "aoi", "aoi", "aoi", "aoi", "word", "word", "word", "word", "word", "word", "gesture", "gesture", "gesture", "aoi", "aoi", "aoi", "aoi", "gesture", "gesture", "aoi", "aoi", "aoi", "gesture", "aoi", "aoi", "aoi", "aoi", "aoi", "aoi", "word", "word", "word", "word", "word", "word", "word", "word", "word", "word", "gesture", "gesture"), MM_annotation = c("*", "B", "*", "C", "A", "*", "C", "*", "A", "A", "what", "'s", "a", "mountain", "for", "you", "prep", "stroke", "hold", "C", "*", "A", "A", "hold", "retract", "*", "A", "A", "retract", "*", "C", "A", "*", "A", "*", "I", "do", "n't", "know", "the", "Schauinsland", "is", "a", "small", "mountain", "stroke", "retract"), start_pm = c(0, 400, 867, 933, 0, 693, 940, 1413, 1560, 0, 0, 333, 447, 600, 1113, 1333, 0, 547, 934, 1813, 2166, 1813, 1813, 1813, 2179, 2367, 2367, 2367, 2367, 3437, 4437, 3437, 3743, 3437, 5737, 3437, 3548, 3660, 3833, 4046, 4200, 4740, 4880, 5000, 5273, 3437, 3950), end_pm = c(400, 867, 933, 1820, 693, 940, 1413, 1560, 1820, 1820, 333, 447, 600, 1113, 1333, 1820, 547, 934, 1813, 2166, 2373, 2380, 2380, 2179, 2366, 3427, 3427, 3427, 3437, 4437, 6057, 3743, 6057, 5737, 6057, 3548, 3660, 3833, 4046, 4200, 4740, 4880, 5000, 5273, 6055, 3950, 4104), max_x = c(2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 2700, 4513, 4513, 4513, 4513, 4513, 4513, 5067, 5067, 5067, 5067, 6137, 6137, 6137, 6137, 6137, 6137, 6137, 6137, 6137, 6137, 6137, 6137, 6137, 6137, 6137, 6137, 6137, 6137)), row.names = c(NA, -47L ), class = c("tbl_df", "tbl", "data.frame"))
解决方案:使用ggnewscale包实现独立颜色比例尺
ggplot默认情况下,同一美学属性(比如color)只能绑定一个比例尺。要实现两个独立的颜色映射,我们可以借助ggnewscale包的new_scale_color()函数,重置颜色比例尺上下文,让第二个geom_segment使用全新的颜色映射规则。
步骤1:安装并加载ggnewscale包
install.packages("ggnewscale") library(ggnewscale)
步骤2:修改绘图代码
核心修改点:
- 第一个
geom_segment仅过滤MM == "aoi"的数据,避免和手势数据混在一起 - 调用
new_scale_color()分隔两个颜色比例尺的作用域 - 第二个
geom_segment使用独立的color映射和比例尺,同时设置图例名称区分两个图例
修改后的完整代码:
library(ggplot2) library(ggnewscale) library(dplyr) ggplot() + # 绘制AOI的segment geom_segment(data = df2 %>% filter(MM == "aoi"), aes(x = start_pm, xend = end_pm, y = MM_by, yend = MM_by, color = MM_annotation), size = 3) + geom_point(data = df2 %>% filter(MM == "aoi"), aes(x = max_x, y = MM_by), alpha = 0) + # 保留虚拟端点 scale_y_discrete(name = NULL, limits = rev) + facet_wrap(~ Utterance, scales = "free_x", ncol = 1) + scale_colour_manual(name = "AOI 类型", values = c("*" = "lemonchiffon", "A" = "darkorange", "B" = "lawngreen", "C" = "slateblue1")) + # 添加分割线和文字(这部分不受颜色比例尺影响) geom_vline(data = df2 %>% filter(MM == "word"), aes(xintercept = end_pm), lty = 3)+ geom_text(data = df2 %>% filter(MM == "word"), aes(x = end_pm -(end_pm - start_pm)/2, label = MM_annotation), size = 3, colour = "white")+ # 重置颜色比例尺上下文,为手势segment准备独立规则 new_scale_color() + # 绘制手势的segment geom_segment(data = df2 %>% filter(MM == "gesture"), aes(x = start_pm, xend = end_pm, y = MM_by, yend = MM_by, color = MM_annotation), size = 3) + scale_colour_manual(name = "手势类型", values = c("prep" = "lightblue", "stroke" = "royalblue", "hold" = "blue", "retract" = "navyblue"))
效果说明
修改后的代码会生成两个独立的图例:
- 第一个图例对应AOI的颜色映射,显示
*、A、B、C对应的颜色 - 第二个图例对应手势的颜色映射,显示
prep、stroke、hold、retract对应的颜色
两个geom_segment的颜色完全独立,不会互相覆盖。
内容的提问来源于stack exchange,提问作者Chris Ruehlemann
相关产品推荐
相关产品推荐

