-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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))

