R语言 ggplot2 与 ggforce 绘制空心饼图:3步代码实现环形图与标签自动布局
在数据可视化领域,饼图(Pie Chart)是最常见的图表类型之一,用于展示各类别在整体中的占比关系。然而传统实心饼图存在标签重叠、空间利用率低等问题,而空心饼图(环形图)不仅能有效解决这些问题,还能在中心区域添加额外信息,提升图表的信息密度和美观度。本文将系统介绍如何利用R语言中的
ggplot2
和
ggforce
包,通过3步核心代码实现专业级空心饼图,并彻底解决标签自动布局难题。
1. 基础准备与环境搭建
在开始绘制高级饼图之前,我们需要确保工作环境准备就绪。R语言拥有丰富的可视化生态系统,其中
ggplot2
是最受欢迎的绘图系统之一,而
ggforce
作为其扩展包,提供了更强大的几何图形和坐标系统控制能力。
首先安装并加载必要的R包:
# 安装必要包(如果尚未安装)
if(!require(ggplot2)) install.packages("ggplot2")
if(!require(ggforce)) install.packages("ggforce")
if(!require(dplyr)) install.packages("dplyr")
if(!require(scales)) install.packages("scales") # 用于百分比格式化
# 加载包
library(ggplot2)
library(ggforce)
library(dplyr)
library(scales)
接下来准备示例数据。我们使用一个包含疾病类型及其占比的数据集:
# 创建示例数据集
disease_data <- data.frame(
disease = c("Heart disease", "Cancer", "Injuries", "CPD", "Stroke",
"Type2 diabetes", "AD", "Suicide", "IP", "Chronic liver disease", "Other"),
ratio = c(24.2, 21.9, 7.6, 5.2, 4.3, 3.2, 2.6, 2.6, 1.8, 1.8, 24.8)
)
# 计算角度信息(为后续标签布局做准备)
disease_data <- disease_data %>%
arrange(desc(disease)) %>%
mutate(
end_angle = 2 * pi * cumsum(ratio)/sum(ratio),
start_angle = lag(end_angle, default = 0),
mid_angle = 0.5 * (start_angle + end_angle)
)
2. 从基础饼图到高级环形图
2.1 传统饼图的局限性
R基础包中的
pie()
函数可以快速绘制简单饼图:
# 基础饼图
pie(disease_data$ratio, labels = disease_data$disease,
col = rainbow(nrow(disease_data)), main = "疾病分布饼图")
然而这种方法存在明显缺陷:
- 颜色管理不便
- 标签容易重叠
- 自定义选项有限
- 难以添加复杂注释
2.2 ggplot2实现基础饼图
使用
ggplot2
可以创建更灵活的饼图。本质上,饼图是堆叠条形图经极坐标转换后的结果:
# ggplot2基础饼图
ggplot(disease_data, aes(x = "", y = ratio, fill = disease)) +
geom_bar(stat = "identity", width = 1, color = "white") +
coord_polar("y", start = 0) +
theme_void() +
scale_fill_brewer(palette = "Set3")
这种实现方式虽然美观,但仍未解决标签重叠问题。手动添加标签需要计算每个扇区的位置:
# 手动添加标签(不推荐)
ggplot(disease_data, aes(x = "", y = ratio, fill = disease)) +
geom_bar(stat = "identity", width = 1, color = "white") +
coord_polar("y", start = 0) +
geom_text(aes(label = paste0(ratio, "%")),
position = position_stack(vjust = 0.5)) +
theme_void()
2.3 使用ggforce创建空心饼图
ggforce
包的
geom_arc_bar()
函数专为环形图设计,通过设置内外半径参数
r0
和
r
轻松创建空心效果:
# 空心饼图核心代码
ggplot() +
geom_arc_bar(data = disease_data,
aes(x0 = 0, y0 = 0, r0 = 0.5, r = 1,
start = start_angle, end = end_angle,
fill = disease),
color = "white") +
coord_fixed() +
theme_void() +
scale_fill_brewer(palette = "Set3")
关键参数说明:
-
x0,y0: 圆心坐标 -
r0: 内半径(设为0时为实心饼图) -
r: 外半径 -
start,end: 扇区的起始和结束角度
3. 标签自动布局的终极解决方案
3.1 标签重叠问题分析
饼图标签布局面临两大挑战:
- 空间有限,特别是当扇区较小时
- 标签需要清晰指向对应的扇区
传统解决方案如
geom_text()
会导致以下问题:
- 小扇区的标签挤在一起
- 长标签超出绘图区域
- 标签与扇区对应关系不明确
3.2 智能标签布局算法
我们实现一个基于极坐标的标签布局系统,包含以下功能:
- 自动计算标签最佳位置
- 智能调整标签偏移量避免重叠
- 添加引导线增强可读性
# 计算标签位置
label_position <- 1.2 # 标签基准半径
label_data <- disease_data %>%
mutate(
x = label_position * sin(mid_angle),
y = label_position * cos(mid_angle),
hjust = ifelse(mid_angle > pi, 1, 0),
vjust = ifelse(mid_angle < pi/2 | mid_angle > 3*pi/2, 0, 1)
)
# 完整绘图代码
ggplot() +
# 绘制空心饼图
geom_arc_bar(data = disease_data,
aes(x0 = 0, y0 = 0, r0 = 0.5, r = 1,
start = start_angle, end = end_angle,
fill = disease),
color = "white") +
# 添加标签
geom_text(data = label_data,
aes(x = x, y = y,
label = paste0(disease, "\n", ratio, "%"),
hjust = hjust, vjust = vjust),
size = 3) +
# 添加引导线
geom_segment(data = label_data,
aes(x = 1.05 * sin(mid_angle),
y = 1.05 * cos(mid_angle),
xend = 0.9 * sin(mid_angle),
yend = 0.9 * cos(mid_angle))) +
coord_fixed() +
theme_void() +
scale_fill_brewer(palette = "Set3") +
labs(title = "疾病分布环形图") +
theme(plot.title = element_text(hjust = 0.5, size = 14))
3.3 高级优化技巧
对于更复杂的场景,我们可以进一步优化:
1. 标签自动避让
使用
ggrepel
包实现标签自动避让:
if(!require(ggrepel)) install.packages("ggrepel")
library(ggrepel)
ggplot() +
geom_arc_bar(data = disease_data,
aes(x0 = 0, y0 = 0, r0 = 0.5, r = 1,
start = start_angle, end = end_angle,
fill = disease),
color = "white") +
geom_text_repel(data = label_data,
aes(x = 1.1 * sin(mid_angle),
y = 1.1 * cos(mid_angle),
label = paste0(disease, ": ", ratio, "%")),
size = 3,
point.padding = NA,
box.padding = 0.4,
segment.color = "grey50") +
coord_fixed() +
theme_void()
2. 交互式可视化
使用
plotly
创建交互式环形图:
if(!require(plotly)) install.packages("plotly")
library(plotly)
p <- ggplot() +
geom_arc_bar(data = disease_data,
aes(x0 = 0, y0 = 0, r0 = 0.5, r = 1,
start = start_angle, end = end_angle,
fill = disease,
text = paste(disease, "<br>占比:", ratio, "%")),
color = "white") +
coord_fixed() +
theme_void()
ggplotly(p, tooltip = "text")
4. 实战案例:多层环形图与高级应用
4.1 多层环形图绘制
通过组合多个
geom_arc_bar
图层,可以创建展示层级结构的多层环形图:
# 创建包含分组信息的数据
grouped_data <- data.frame(
group = rep(c("Group1", "Group2", "Group3"), times = c(4, 3, 4)),
category = LETTERS[1:11],
value = c(10, 15, 12, 8, 20, 15, 10, 5, 12, 18, 15)
) %>%
group_by(group) %>%
mutate(
group_total = sum(value),
group_ratio = value/group_total,
end_angle = 2 * pi * cumsum(group_ratio)/sum(group_ratio),
start_angle = lag(end_angle, default = 0)
) %>%
ungroup() %>%
mutate(
global_end = 2 * pi * cumsum(value)/sum(value),
global_start = lag(global_end, default = 0)
)
# 绘制多层环形图
ggplot() +
# 外层(分组级别)
geom_arc_bar(data = grouped_data,
aes(x0 = 0, y0 = 0, r0 = 0.7, r = 1,
start = global_start, end = global_end,
fill = group),
color = "white", alpha = 0.7) +
# 内层(类别级别)
geom_arc_bar(data = grouped_data,
aes(x0 = 0, y0 = 0, r0 = 0.2, r = 0.6,
start = start_angle, end = end_angle,
fill = category),
color = "white") +
coord_fixed() +
theme_void() +
scale_fill_manual(values = c(brewer.pal(3, "Set1"), brewer.pal(11, "Set3")))
4.2 环形图最佳实践
根据实际项目经验,总结以下环形图使用建议:
-
类别数量控制
- 理想情况:3-7个类别
- 最多不超过12个类别
- 过多样式会导致视觉混乱
-
颜色选择原则
- 使用色盲友好配色
- 相邻扇区使用对比色
- 重要数据使用突出颜色
# 专业配色方案示例
professional_colors <- c(
"#4E79A7", "#A0CBE8", "#F28E2B", "#FFBE7D", "#59A14F",
"#8CD17D", "#B6992D", "#F1CE63", "#499894", "#86BCB6",
"#E15759", "#FF9D9A", "#79706E", "#BAB0AC", "#D37295",
"#FABFD2", "#B07AA1", "#D4A6C8", "#9D7660", "#D7B5A6"
)
-
标签优化技巧
- 重要数据添加百分比
- 长名称使用缩写
- 极小扇区合并为"其他"
-
动画与交互增强
- 添加悬停提示
- 实现点击筛选
- 创建动画过渡效果
4.3 环形图与饼图的对比选择
虽然环形图更加美观,但选择图表类型时应考虑以下因素:
| 比较维度 | 传统饼图 | 空心环形图 |
|---|---|---|
| 空间利用率 | 较低 | 较高(中心可用) |
| 标签清晰度 | 容易重叠 | 更易布局 |
| 多维度展示 | 单一维度 | 支持多层结构 |
| 视觉重点 | 整体比例 | 强调部分对比 |
| 小数据展示 | 适合 | 可能显得空洞 |
| 动态效果 | 一般 | 更易实现动效 |
在实际项目中,我通常会根据以下标准决策:
- 展示3-5个主要类别时使用饼图
- 需要对比多个相关维度时使用多层环形图
- 有大量小类别时考虑堆叠条形图替代
5. 性能优化与常见问题解决
5.1 大数据量优化
当数据量较大时,环形图渲染可能变慢。以下优化策略效果显著:
-
数据聚合
- 合并小扇区(如<2%合并为"其他")
- 使用数据分箱
# 小类别合并示例
disease_data_optimized <- disease_data %>%
mutate(
disease = ifelse(ratio < 3, "Other", as.character(disease))
) %>%
group_by(disease) %>%
summarise(ratio = sum(ratio)) %>%
mutate(
end_angle = 2 * pi * cumsum(ratio)/sum(ratio),
start_angle = lag(end_angle, default = 0)
)
-
渲染优化
- 减少不必要的图形元素
- 简化颜色方案
- 关闭抗锯齿(对于静态导出)
5.2 常见问题排查
问题1:扇区间距不均匀
解决方案:检查角度计算是否正确,确保
start_angle
和
end_angle
连续无重叠
问题2:标签位置错乱
解决方案:确认
mid_angle
计算正确,检查极坐标转换
# 正确的角度计算流程
data <- data %>%
arrange(desc(category)) %>% # 确保排序正确
mutate(
cum_ratio = cumsum(ratio),
end_angle = 2 * pi * cum_ratio/sum(ratio),
start_angle = lag(end_angle, default = 0),
mid_angle = 0.5*(start_angle + end_angle)
)
问题3:图例与颜色不符
解决方案:统一颜色映射,确保
scale_fill_*
与数据匹配
# 确保颜色映射一致
ggplot() +
geom_arc_bar(aes(..., fill = category)) +
scale_fill_manual(values = setNames(color_vector, unique(data$category)))
5.3 输出与分享
高质量输出需要注意以下参数:
# PDF输出(适合印刷)
ggsave("ring_chart.pdf", width = 10, height = 8, units = "in", dpi = 300)
# PNG输出(适合网页)
ggsave("ring_chart.png", width = 1600, height = 1200, units = "px", bg = "white")
# 交互式HTML输出
library(htmlwidgets)
saveWidget(ggplotly(p), "interactive_ring.html")

2073

被折叠的 条评论
为什么被折叠?



