I have fixed this, here is the code I used. I've built a new layout option in my package ggalign that arranges multiple plots in circular tracks. Each plot fits into its designated circle track while sharing the same coordinate origin (0, 0)
based on this idea.
draw_ggcircle_list <- function(plot_list, start = 0L, end = 2 * pi,
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
plot_table <- origin <- 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 = start, end = end,
inner.radius = plot_inner[[i]] / plot_size,
r.axis.inside = TRUE
) +
ggplot2::labs(x = NULL, y = 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)
# 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 <- layout$coord$transform(
# data.frame(x = 0.5, y = 0.5),
# panel_params = layout$panel_params[[1L]]
# )
# For bbox, `ggplot2::polar_bbox` always take (0.5, 0.5) as origin
bbox <- layout$panel_params[[1L]]$bbox
just <- c(
scales::rescale(0.5, from = bbox$x),
scales::rescale(0.5, from = bbox$y)
)
if (is.null(plot_table)) {
plot_table <- gt
} else {
# define the panel size of the inner track
rescale_factor <- last_plot_size / plot_size
# just using panel spacing as the spacer between two plots
spacing <- ggplot2::calc_element("panel.spacing.y", theme)
plot_table <- grid::editGrob(plot_table, vp = grid::viewport(
width = grid::unit(rescale_factor, "npc") - spacing,
height = grid::unit(rescale_factor, "npc") - spacing,
x = origin[1L], y = origin[2L],
just = just,
default.units = "native",
clip = "off"
))
# 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 = "inner-track"
)
}
origin <- just
last_plot_size <- plot_size # the last plot panel size
}
plot_table
}
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())
grid::grid.draw(draw_ggcircle_list(list(p1, p1)))