Skip to content

Feature request: helper method in coord_radial to get the coord origin and add implicit option to always include coord origin in the viewport #6261

Closed
@Yunuuuu

Description

@Yunuuuu

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)))

Image

dev.off()
grid::grid.draw(draw_ggcircle_list(list(p1, p1), end = 1 / 4 * pi))

Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions