R/APOTCPlot.R

Defines functions subset_to_only_edge_circles get_apotc_plot_dims_from_df get_apotc_plot_dims APOTCPlot_error_handler APOTCPlot

Documented in APOTCPlot

#' @title Various variations of visualizations of clonal expansion post-RunAPOTC
#'
#' @description
#' `r lifecycle::badge("stable")`
#'
#' Given a seurat object with an 'apotc' (APackOfTheClones) object
#' from running [RunAPOTC()], this function will read the information and return
#' a customizable ggplot2 object of the clonal expansion with a circle size
#' legend. If the user is unhappy about certain aspects of the plot, many
#' parameters can be adjusted with the [AdjustAPOTC] function.
#'
#' The specific APackOfTheClones run to be plotted can be identified in two
#' ways: either by inputting the `run_id` associated with the run that was
#' either defined / auto-generated during [RunAPOTC()], or by inputting the
#' `reduction_base`, `clonecall`, `extra_filter` and any other keyword arguments
#' that corresponded to the run. Its heavily recommended to use the `run_id`.
#' If none of these parameters are inputted, the function defaults to returning
#' the plot of the latest run.
#'
#' @inheritParams RunAPOTC
#'
#' @param seurat_obj A seurat object that has been integrated with clonotype
#' data and has had a valid run of [RunAPOTC()].
#' @param show_shared The output of [getSharedClones] can be inputted here,
#' and the resulting plot will overlay lines between clone circles if that
#' clonotype is common between clusters. Note that the input ***must*** be
#' generated from data in the correct `APackOfTheClones` run, and the behavior
#' is undefined otherwise and will likely error. The next 4 arguments allow for
#' aesthetic customization of these line links.
#' @param only_link Optional integer indicating to only display clone
#' links originating from this cluster if showing shared clones.
#' @param show_all_links `r lifecycle::badge("experimental")` logical. If
#' `TRUE`, will show all links between
#' clusters. Defaults to `FALSE` which only shows links between some clusters
#' without overlapping, although there is currently no way to control which.
#' @param clone_link_width numeric. The width of the lines that connect shared
#' clones. Defaults to `"auto"` which will estimate a reasonable value depending
#' on circle sizes.
#' @param clone_link_color character. The color of the lines that connect shared
#' clones. Defaults to `"blend"` which will use the average colors of the two
#' connected clones. Else, any hex color or valid color string input will work,
#' and the corresponding color will be applied on all links.
#' @param clone_link_alpha numeric. The alpha of the lines that connect shared
#' clones.
#' @param res The number of points on the generated path per full circle. From
#' plot viewers, if circles seem slightly too pixelated, it is recommended to
#' first try to export the plot as an `.svg` before increasing `res` due to
#' increased plotting times from [ggforce::geom_circle].
#' @param linetype The type of outline each circle should have. defaults to
#' `"blank` meaning no outline. More information is in the function
#' documentation of `ggforce::geom_circle`.
#' @param use_default_theme logical that defaults to `TRUE`. If `TRUE`,
#' the resulting plot will have the same theme as the seurat reference reduction
#' plot. Else, the plot will simply have a blank background.
#' @param retain_axis_scales If `TRUE`, approximately maintains the axis scales
#' of the original reduction plot. However, it will only attempt to extend the
#' axes and never shorten. Users are recommended to set this to `TRUE`
#' especially if working with subsetted versions of the clonal data to better
#' preserve the geometric relation to the original dimensional reduction.
#' @param alpha numeric. The alpha of the circles in (0, 1]. Defaults to 1.
#' @param show_labels If `TRUE`, will label each circle cluster at the centroid,
#' defaulting to "C0, C1, ...".
#' @param label_size The text size of labels if shown. Defaults to 5.
#' @param add_size_legend If `TRUE`, adds a legend to the plot visualizing the
#' relative sizes of clones. Note that it is simply an overlay and not a real
#' ggplot2 legend.
#' @param legend_sizes numeric vector. Indicates the circle sizes to be
#' displayed on the legend, and will always be sorted from smallest to greatest.
#' Defaults to `"auto"` which estimate a reasonable range of sizes to display.
#' @param legend_position character or numeric. Can be set to either
#' `"top_left"`, `"top_right"`, `"bottom_left"`, `"bottom_right"` and places the
#' legend roughly in the corresponding position. Otherwise, can be a numeric
#' vector of length 2 indicating the x and y position of the *topmost (smallest)
#' circle* of the legend.
#' @param legend_buffer numeric. Indicates how much to "push" the legend towards
#' the center of the plot from the selected corner. If negative, will push away
#' @param legend_color character. Indicates the hex color of the circles
#' displayed on the legend. Defaults to the hex code for a gray tone
#' @param legend_spacing numeric. Indicates the horizontal distance between each
#' stacked circle on the size legend. Defaults to `"auto"` which will use an
#' estimated value depending on plot size
#' @param legend_label character. The title of the legend, which defaults to
#' `"clone sizes`.
#' @param legend_text_size numeric. The text size of the letters and numbers on
#' the legend
#' @param add_legend_background logical. If `TRUE`, will add a border around the
#' legend and fill the background to be white, overlaying anything else.
#' @param add_legend_centerspace numeric. An additional amount of distance
#' changed between the circle sizes on the left side of the legend and the
#' numbers on the right. Useful to set to around 0.5 (or more / less) when there
#' are particularly large clone sizes that may cover the numbers.
#' @param detail logical. If `FALSE`, will only plot entire clusters as one
#' large circle, which may be useful in cases where there are a high number
#' of clones resulting in a large number of circles on the resulting ggplot,
#' which has increased plotting times, and certain aspects of the plot needs
#' to be finely adjusted with [AdjustAPOTC] or simply inspected. This should
#' not be set to `FALSE` for the actual clonal expansion plot.
#'
#' @return A ggplot object of the APackOfTheClones clonal expansion plot of the
#' seurat object. There is an additional 10th element in the object named
#' `"APackOfTheClones"` used by other functions in this package and shouldn't
#' interfere with any other ggplot functionality. (As far as currently known)
#' @export
#'
#' @seealso [AdjustAPOTC]
#'
#' @examples
#' data("combined_pbmc")
#'
#' combined_pbmc <- RunAPOTC(
#'     combined_pbmc, run_id = "run1", verbose = FALSE
#' )
#'
#' # plotting with default arguments will plot the latest "run1"
#' clonal_packing_plot <- APOTCPlot(combined_pbmc)
#'
APOTCPlot <- function(
    seurat_obj,
    reduction_base = NULL,
    clonecall = NULL,
    ...,
    extra_filter = NULL,
    run_id = NULL,

    show_shared = NULL,
    only_link = NULL, # TODO this should work with a string cluster name! Perhaps inferred from Ident.
    show_all_links = FALSE,
    clone_link_width = "auto",
    clone_link_color = "black",
    clone_link_alpha = 0.5,

    res = 360L,
    linetype = "blank",
    use_default_theme = TRUE,
    retain_axis_scales = FALSE,
    alpha = 1,

    show_labels = FALSE,
    label_size = 5,

    add_size_legend = TRUE,
    legend_sizes = "auto",
    legend_position = "auto",
    legend_buffer = 0.2,
    legend_color = "#808080",
    legend_spacing = "auto",
    legend_label = "Clone sizes",
    legend_text_size = 5,
    add_legend_background = TRUE,
    add_legend_centerspace = 0,

    detail = TRUE,

    verbose = TRUE
) {
    # handle varargs, run_id, and typecheck
    varargs_list <- list(...)
    args <- environment()
    args$run_id <- infer_object_id_if_needed(args, varargs_list)
    APOTCPlot_error_handler(args)

    # get the apotc object
    apotc_obj <- getApotcData(seurat_obj, args$run_id)

    # initialize plot
    result_plot <- create_initial_apotc_plot(
        apotc_obj, res, linetype, alpha, detail
    )
    result_plot_dimensions <- get_apotc_plot_dims(apotc_obj)

    #set theme
    if (use_default_theme) {
        result_plot <- add_default_theme(
            plt = result_plot,
            reduction = get_reduction_base(apotc_obj)
        )
    } else {
        result_plot <- result_plot + ggplot2::theme_void()
    }

    # retain axis scales on the resulting plot.
    if (retain_axis_scales) {
        result_plot_dimensions <- get_retain_scale_dims(
            seurat_obj,
            reduction = get_reduction_base(apotc_obj),
            ball_pack_plt = result_plot,
            plot_dims = result_plot_dimensions
        )

        result_plot <- result_plot + ggplot2::expand_limits(
            x = get_xr(result_plot_dimensions),
            y = get_yr(result_plot_dimensions)
        )
    }

    if (isnt_empty(show_shared)) {

        # check only_link indexing
        if (!is.null(only_link) &&
            !is_valid_nonempty_cluster(apotc_obj, only_link)) {
            warning(call. = FALSE,
                "* The cluster at index `only_link` = ", only_link,
                " is empty or isn't between 1 ~ ", get_num_clusters(apotc_obj)
            )
            only_link <- NULL
        }

        result_plot <- overlay_shared_clone_links(
            apotc_obj = apotc_obj,
            shared_clones = show_shared,
            result_plot = result_plot,
            only_cluster = only_link,
            link_color_mode = clone_link_color,
            link_width = clone_link_width,
            link_alpha = clone_link_alpha,
            show_all_links = show_all_links, # TODO
            verbose = verbose
            # TODO other params in the future
        )
    }

    if (show_labels) {
        result_plot <- insert_labels(result_plot, apotc_obj, label_size)
    }

    if (add_size_legend) {
        result_plot <- insert_legend(
            plt = result_plot,
            plt_dims = result_plot_dimensions,
            apotc_obj = apotc_obj,
            sizes = legend_sizes,
            pos = legend_position,
            buffer = legend_buffer,
            additional_middle_spacing = add_legend_centerspace,
            color = legend_color,
            n = res,
            spacing = legend_spacing,
            legend_label = legend_label,
            legend_textsize = legend_text_size,
            do_add_legend_border = add_legend_background,
            linetype = linetype
        )
    }

    result_plot <- ApotcGGPlot(result_plot, apotc_obj)

    if (verbose) message("* generated ggplot object")
    result_plot
}

APOTCPlot_error_handler <- function(args) {

    check_apotc_identifiers(args)
    check_filtering_conditions(args)

    # check object_id validity
    if (!containsApotcRun(args$seurat_obj, args$run_id)) {
        stop(call. = FALSE, paste(
            "APackOfTheClones object with id", args$run_id,
            "does not exist in the seurat object"
        ))
    }

    # typecheck clone link args
    typecheck(args$show_shared, is_output_of_getSharedClones, is.null)
    typecheck(args$only_link, is_an_integer, is.null)
    if (!should_estimate(args$clone_link_width))
        typecheck(args$clone_link_width, is_a_positive_numeric)
    typecheck(args$clone_link_color, is_a_character)
    typecheck(args$clone_link_alpha, is_a_numeric)

    # typecheck visualization args
    typecheck(args$res, is_an_integer)
    typecheck(args$linetype, is_a_character)
    typecheck(args$use_default_theme, is_a_logical)
    typecheck(args$retain_axis_scales, is_a_logical)
    typecheck(args$show_labels, is_a_logical)
    typecheck(args$label_size, is_a_positive_numeric)

    # check legend args
    typecheck(args$add_size_legend, is_a_logical)
    check_legend_params(args)

    typecheck(args$detail, is_a_logical)

}

# helpers for getting plot dimensions quickly

get_apotc_plot_dims <- function(apotc_obj) {
    apotc_obj %>%
        get_plottable_df_with_color() %>%
        get_apotc_plot_dims_from_df()
}

get_apotc_plot_dims_from_df <- function(plot_dataframe) {
    plot_dataframe %>%
        subset_to_only_edge_circles() %>%
        plot_clusters() %>%
        get_plot_dims()
}

subset_to_only_edge_circles <- function(apotc_plot_dataframe) {
    apotc_plot_dataframe[
        unique(rcppGetEdgeCircleindices(apotc_plot_dataframe)),
    ]
}

Try the APackOfTheClones package in your browser

Any scripts or data that you put into this service are public.

APackOfTheClones documentation built on Aug. 18, 2025, 5:36 p.m.