I want to align two circular plot in ggplot2 (draw_ggcircle_list
), but I cannot extract the coordinate origin (0, 0) of the coord_radial()
. Now following two plots won't aligned by the x-axis (theta-axis), since the inner plot is not put in the coordinate origin of the outter plot:
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))
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)))