Description
The coord_radial feature in ggplot2 is a powerful tool for creating circular visualizations, and 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)
.
The core idea is to place each plot in its own circular track around the same coordinate origin (0, 0)
, resizing them to fit. By using the same radial coordinate system for all plots. But this failed when we using acute angle (radius < pi / 2)
since the plot won't include the coord (0, 0)
, I’m wondering if an implicit option could be added to CoordRadial
to make this method more versatile, and whether a helper function in CoordRadial
to retrieve the coordinate origin (0, 0)
might improve the approach.
# copy your code to the clipboard and run:
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)))
dev.off()
grid::grid.draw(draw_ggcircle_list(list(p1, p1), end = 1 / 4 * pi))