You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 11:25:52