如何提取coord_radial()的坐标原点以对齐ggplot2环形图?
ggplot2环形图theta轴对齐问题修复
问题背景
使用自定义函数draw_ggcircle_list拼接两个环形图时,x轴(theta轴)无法对齐,核心原因是无法正确匹配coord_radial()的坐标原点,导致内层图未定位到外层图的原点上,最终出现错位效果:

原代码如下:
library(ggplot2) # draw_circle_list will convert all plot into polar coordinate p1 <- ggplot(mpg, aes(class, displ)) + geom_boxplot() + theme(plot.background = element_rect(fill = "red")) + ggplot2::guides( theta = ggplot2::guide_axis_theta(angle = 0), r = ggplot2::guide_axis(angle = 0) ) + ggplot2::theme(axis.line.theta = ggplot2::element_line()) draw_ggcircle_list <- function(plot_list, inner_radius = 0.1) { sizes <- rep_len(1, length(plot_list)) # for every plot track, all relative to the total radius `1` plot_track <- sizes / sum(sizes) * (1 - inner_radius) plot_sizes <- 1 - cumsum(c(0, plot_track[-length(plot_track)])) plot_inner <- plot_sizes - plot_track vp <- plot_table <- NULL for (i in rev(seq_along(plot_list))) { # from inner-most to the out-most plot_size <- plot_sizes[[i]] plot <- .subset2(plot_list, i) + ggplot2::coord_radial( start = 0.25 * pi, end = 1.75 * pi, inner.radius = plot_inner[[i]] / plot_size, r.axis.inside = TRUE ) + ggplot2::ylab(NULL) # copied from `ggplot2:::ggplot_gtable` data <- ggplot2::ggplot_build(plot) plot <- data$plot layout <- data$layout data <- data$data theme <- ggplot2:::plot_theme(plot$theme) geom_grobs <- ggplot2:::by_layer( function(l, d) l$draw_geom(d, layout), plot$layers, data, "converting geom to grob" ) gt <- layout$render(geom_grobs, data, theme, plot$labels) if (is.null(plot_table)) { plot_table <- gt } else { # for each inner gtable, we insert it to the panel area of the # outter gtable # how to get the coordinate origin from the `coord_radial()` ? origin <- c(0.5, 0.5) # extract the panel size of the inner track panel_loc <- ggplot2::find_panel(plot_table) width <- as.numeric(plot_table$widths[.subset2(panel_loc, "l")]) * panel_size / plot_size height <- as.numeric(plot_table$heights[.subset2(panel_loc, "t")]) * panel_size / plot_size vp <- grid::viewport( width = grid::unit(width, "npc"), height = grid::unit(height, "npc"), x = origin[1L], y = origin[2L], default.units = "native" ) plot_table$vp <- vp # add the inner track to the panel area of the outter track out_panel <- ggplot2::find_panel(gt) plot_table <- gtable::gtable_add_grob( gt, plot_table, t = .subset2(out_panel, "t"), l = .subset2(out_panel, "l"), b = .subset2(out_panel, "b"), r = .subset2(out_panel, "r"), name = paste("track", i, sep = "-") ) } panel_size <- plot_size # the last plot panel size } grid::grid.newpage() grid::pushViewport(grid::viewport()) grid::grid.draw(plot_table) } draw_ggcircle_list(list(p1, p1))
修复方案
问题根源在于视口(viewport)的定位和缩放逻辑错误,以及gtable插入顺序不当。以下是修正后的代码:
library(ggplot2) library(grid) library(gtable) # 基础环形图示例 p1 <- ggplot(mpg, aes(class, displ)) + geom_boxplot() + theme(plot.background = element_rect(fill = "red")) + guides( theta = guide_axis_theta(angle = 0), r = guide_axis(angle = 0) ) + theme(axis.line.theta = element_line()) draw_ggcircle_list <- function(plot_list, inner_radius = 0.1) { sizes <- rep_len(1, length(plot_list)) # 计算每个环形的半径范围(相对总半径1) plot_track <- sizes / sum(sizes) * (1 - inner_radius) plot_sizes <- 1 - cumsum(c(0, plot_track[-length(plot_track)])) plot_inner <- plot_sizes - plot_track plot_table <- NULL for (i in rev(seq_along(plot_list))) { # 从外层到内层依次构建 plot_size <- plot_sizes[[i]] current_plot <- .subset2(plot_list, i) + coord_radial( start = 0.25 * pi, end = 1.75 * pi, inner.radius = plot_inner[[i]] / plot_size, r.axis.inside = TRUE ) + ylab(NULL) # 直接用ggplot_gtable构建可视化表格,无需手动复制内部逻辑 gt <- ggplot_gtable(ggplot_build(current_plot)) if (is.null(plot_table)) { plot_table <- gt } else { # 获取外层图的面板位置 outer_panel <- find_panel(gt) # 计算内层图的缩放比例:匹配外层图的半径范围 scale <- plot_size / plot_sizes[i+1] # 创建对齐原点的视口:中心为面板中心(即coord_radial的原点) vp <- viewport( width = unit(scale, "npc"), height = unit(scale, "npc"), x = unit(0.5, "npc"), y = unit(0.5, "npc") ) # 给内层图绑定视口 plot_table$vp <- vp # 将内层图嵌入到外层图的面板区域 plot_table <- gtable_add_grob( gt, plot_table, t = outer_panel$t, l = outer_panel$l, b = outer_panel$b, r = outer_panel$r, name = paste("track", i, sep = "-") ) } panel_size <- plot_size } grid.newpage() grid.draw(plot_table) } # 测试修正后的函数 draw_ggcircle_list(list(p1, p1))
关键修改点
- 简化gtable构建:直接使用
ggplot_gtable(ggplot_build(current_plot))替代手动复制ggplot内部逻辑,避免潜在错误。 - 修正视口定位:将视口单位改为
npc,以面板中心(0.5,0.5)作为原点,确保内层图与外层图的coord_radial原点对齐。 - 正确计算缩放比例:基于环形的半径范围计算缩放因子,保证内层图的尺寸与外层图的半径匹配,避免错位。
- 优化gtable插入逻辑:确保内层图正确嵌入到外层图的面板区域,维持层级关系。
内容的提问来源于stack exchange,提问作者Yun
相关产品推荐
相关产品推荐

