R语言 ggplot2 与 ggforce 绘制空心饼图:3步代码实现环形图与标签自动布局

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 标签重叠问题分析

饼图标签布局面临两大挑战:

  1. 空间有限,特别是当扇区较小时
  2. 标签需要清晰指向对应的扇区

传统解决方案如 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 环形图最佳实践

根据实际项目经验,总结以下环形图使用建议:

  1. 类别数量控制

    • 理想情况:3-7个类别
    • 最多不超过12个类别
    • 过多样式会导致视觉混乱
  2. 颜色选择原则

    • 使用色盲友好配色
    • 相邻扇区使用对比色
    • 重要数据使用突出颜色
# 专业配色方案示例
professional_colors <- c(
  "#4E79A7", "#A0CBE8", "#F28E2B", "#FFBE7D", "#59A14F",
  "#8CD17D", "#B6992D", "#F1CE63", "#499894", "#86BCB6",
  "#E15759", "#FF9D9A", "#79706E", "#BAB0AC", "#D37295",
  "#FABFD2", "#B07AA1", "#D4A6C8", "#9D7660", "#D7B5A6"
)
  1. 标签优化技巧

    • 重要数据添加百分比
    • 长名称使用缩写
    • 极小扇区合并为"其他"
  2. 动画与交互增强

    • 添加悬停提示
    • 实现点击筛选
    • 创建动画过渡效果

4.3 环形图与饼图的对比选择

虽然环形图更加美观,但选择图表类型时应考虑以下因素:

比较维度 传统饼图 空心环形图
空间利用率 较低 较高(中心可用)
标签清晰度 容易重叠 更易布局
多维度展示 单一维度 支持多层结构
视觉重点 整体比例 强调部分对比
小数据展示 适合 可能显得空洞
动态效果 一般 更易实现动效

在实际项目中,我通常会根据以下标准决策:

  • 展示3-5个主要类别时使用饼图
  • 需要对比多个相关维度时使用多层环形图
  • 有大量小类别时考虑堆叠条形图替代

5. 性能优化与常见问题解决

5.1 大数据量优化

当数据量较大时,环形图渲染可能变慢。以下优化策略效果显著:

  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)
  )
  1. 渲染优化
    • 减少不必要的图形元素
    • 简化颜色方案
    • 关闭抗锯齿(对于静态导出)

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")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值