在ggplot2的coord_polar中绘制跨零度的geom_segment问题求助
问题:极坐标环形图中跨零度的线段绘制异常
我正在修改一个环形条形图教程,制作年度环形日历并按季节划分。但冬季对应的geom_segment在极坐标图中跨越零度,从12月到2月的线段会沿逆时针绘制,而非顺时针,导致和其他季节的线段相交。如果从1月开始划分季节,线段和标签就不会相交,所以确定跨零是问题根源。
效果对比
- 移除冬季后的正确效果:

- 包含冬季时的异常效果(线段相交):

我的代码
library(ggplot2) library(tidyverse) library(viridis) library(forcats) library(data.table) library(dplyr) # Create dataset data <- data.frame( month=factor(c("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")), group=factor(c( rep('WI', 3), rep('SP', 3), rep('SU', 3), rep('FA', 3))) , value1=1.5, value2=5, value3=1.5, value4=10, value5=20, value6=30 ) # Transform data in a tidy format (long format) data <- data %>% gather(key = "observation", value="value", -c(1,2)) # Get the name and the y position of each label label_data <- data %>% group_by(month = factor(month, levels = unique(month))) %>% summarize(tot=sum(value)) number_of_bar <- nrow(label_data) label_data <- rownames_to_column(label_data) names(label_data)[names(label_data) == "rowname"] <- "id" label_data$id <- as.numeric(label_data$id) angle <- 90 - 360 * (label_data$id-0.5) /number_of_bar # 调整角度让标签在柱子中心 label_data$hjust <- ifelse(angle < -90, 1, 0) label_data$month <- c("J","F","M","A","M","J","J","A","S","O","N","D") group <- c("WI","SP","SU","FA") start <- c(12,3,6,9) end <- c(2,5,8,11) base_data <- data.frame(group,start,end) base_data <- base_data %>% mutate(title = (start + end)/2) # base_data <- base_data[-1,] # 注释掉此行会移除冬季线段和标签,保留则出现问题 # prepare a data frame for grid (scales) grid_data <- base_data grid_data$end <- grid_data$end[ c( nrow(grid_data), 1:nrow(grid_data)-1)] + 1 grid_data$start <- grid_data$start - 1 grid_data <- grid_data[-1,] p <- ggplot(data) + # Add the stacked bar geom_bar(aes(x=factor(month), y=value, fill=observation), stat="identity", alpha=0.5) + scale_fill_viridis_d() + ylim(-30,70) + theme_minimal() + theme( legend.position = "none", axis.text = element_blank(), axis.title = element_blank(), panel.grid = element_blank(), plot.margin = unit(rep(-1,4), "cm") ) + coord_polar() + # Add labels on top of each bar geom_text(data=label_data, aes(x=id, y=63.5, label=month, hjust=hjust), color="black", fontface="bold",alpha=0.6, size=4, inherit.aes = FALSE ) + # Add base line information geom_segment(data=base_data, aes(x = start, y = -5, xend = end, yend = -5), colour = "black", alpha=0.8, size=0.6 , inherit.aes = FALSE ) + geom_text(data=base_data, aes(x = title, y = -10, label=group), colour = "black", alpha=0.8, size=4, fontface="bold", inherit.aes = FALSE) p
解决方案
问题核心是极坐标下,当x起始值大于结束值时,geom_segment会默认走逆时针的短路径。冬季的start=12、end=2正好触发这个逻辑,导致线段反向绘制。解决办法是把跨零的线段拆成两段:一段从12到13(对应环形的0点),另一段从1到2,强制线段沿顺时针方向连接。
修改步骤
- 调整
base_data的生成逻辑,自动识别并拆分跨零的季节段:
# 处理跨零的季节段 base_data <- data.frame(group,start,end) %>% mutate( # 标记是否跨零 cross_zero = start > end, # 跨零季节的标题位置需要调整,让标签落在顺时针区间中间 title = ifelse(cross_zero, (start + end + 12)/2, (start + end)/2) ) # 拆分跨零的行 split_rows <- base_data %>% filter(cross_zero) %>% summarise( group = group, start = c(start, 1), end = c(13, end), # 13对应环形的0位置,实现从12到0的连接 cross_zero = TRUE, title = first(title) ) # 合并拆分后的行和非跨零行 base_data <- bind_rows( base_data %>% filter(!cross_zero), split_rows )
- 季节标签需要去重,避免拆分后重复显示:
# 替换原有的geom_text代码 geom_text(data=base_data %>% distinct(group, title), aes(x = title, y = -10, label=group), colour = "black", alpha=0.8, size=4, fontface="bold", inherit.aes = FALSE)
完整修改后的代码
library(ggplot2) library(tidyverse) library(viridis) library(forcats) library(data.table) library(dplyr) # Create dataset data <- data.frame( month=factor(c("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")), group=factor(c( rep('WI', 3), rep('SP', 3), rep('SU', 3), rep('FA', 3))) , value1=1.5, value2=5, value3=1.5, value4=10, value5=20, value6=30 ) # Transform data in a tidy format (long format) data <- data %>% gather(key = "observation", value="value", -c(1,2)) # Get the name and the y position of each label label_data <- data %>% group_by(month = factor(month, levels = unique(month))) %>% summarize(tot=sum(value)) number_of_bar <- nrow(label_data) label_data <- rownames_to_column(label_data) names(label_data)[names(label_data) == "rowname"] <- "id" label_data$id <- as.numeric(label_data$id) angle <- 90 - 360 * (label_data$id-0.5) /number_of_bar # 调整角度让标签在柱子中心 label_data$hjust <- ifelse(angle < -90, 1, 0) label_data$month <- c("J","F","M","A","M","J","J","A","S","O","N","D") group <- c("WI","SP","SU","FA") start <- c(12,3,6,9) end <- c(2,5,8,11) # 处理跨零的季节段 base_data <- data.frame(group,start,end) %>% mutate( cross_zero = start > end, title = ifelse(cross_zero, (start + end + 12)/2, (start + end)/2) ) # 拆分跨零的行 split_rows <- base_data %>% filter(cross_zero) %>% summarise( group = group, start = c(start, 1), end = c(13, end), cross_zero = TRUE, title = first(title) ) # 合并数据 base_data <- bind_rows( base_data %>% filter(!cross_zero), split_rows ) # prepare a data frame for grid (scales) grid_data <- base_data %>% filter(!cross_zero) # 网格数据不需要拆分后的行 grid_data$end <- grid_data$end[ c( nrow(grid_data), 1:nrow(grid_data)-1)] + 1 grid_data$start <- grid_data$start - 1 grid_data <- grid_data[-1,] p <- ggplot(data) + # Add the stacked bar geom_bar(aes(x=factor(month), y=value, fill=observation), stat="identity", alpha=0.5) + scale_fill_viridis_d() + ylim(-30,70) + theme_minimal() + theme( legend.position = "none", axis.text = element_blank(), axis.title = element_blank(), panel.grid = element_blank(), plot.margin = unit(rep(-1,4), "cm") ) + coord_polar() + # Add labels on top of each bar geom_text(data=label_data, aes(x=id, y=63.5, label=month, hjust=hjust), color="black", fontface="bold",alpha=0.6, size=4, inherit.aes = FALSE ) + # Add base line information geom_segment(data=base_data, aes(x = start, y = -5, xend = end, yend = -5), colour = "black", alpha=0.8, size=0.6 , inherit.aes = FALSE ) + # 季节标签去重,只显示一次 geom_text(data=base_data %>% distinct(group, title), aes(x = title, y = -10, label=group), colour = "black", alpha=0.8, size=4, fontface="bold", inherit.aes = FALSE) p
修改后,冬季线段会沿顺时针方向连接12月到2月,不会和其他季节线段相交,季节标签也会正确显示在对应区间的中间位置。
内容的提问来源于stack exchange,提问作者ElizaBeso000
相关产品推荐
相关产品推荐

