Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cross layout #33

Merged
merged 33 commits into from
Dec 1, 2024
Merged

Cross layout #33

merged 33 commits into from
Dec 1, 2024

Conversation

Yunuuuu
Copy link
Owner

@Yunuuuu Yunuuuu commented Nov 30, 2024

No description provided.

@Yunuuuu
Copy link
Owner Author

Yunuuuu commented Dec 1, 2024

I have tried to use three methods to implement cross_layout

  1. A new layout system with left and right hand of the connected layout, but we want to use nested layout in the cross_layout(), which makes it hard between the nested layout and the main plot.
  2. A generic function which can accepts two layout:
#' @include layout-.R
methods::setClass(
    "CrossLayout",
    contains = "Layout",
    list(
        left = "ANY",
        right = "ANY",
        direction = "character",
        plot = "ANY",
        size = "ANY",
        sizes = "ANY"
    ),
    prototype = list(left = NULL, right = NULL)
)

new_cross_layout <- function(left, right, direction, mapping = aes(),
                             size = NA, theme = NULL, sizes = NA) {
    call <- caller_call()
    arg1 <- caller_arg(left) # nolint
    arg2 <- caller_arg(right) # nolint
    if (!is.null(theme)) assert_s3_class(theme, "theme", call = call)
    # if the left layout is a `cross_layout()`, we get the right coords
    left_coords <- get_layout_coords(left,
        direction = direction, hand = "right"
    )
    if (is.null(left_coords)) {
        cli_abort(c(
            sprintf("{.arg {arg1}} cannot be a %s", object_name(left)),
            i = "{.arg {arg1}} must align observations in {.field {direction}}."
        ), call = call)
    } else if (is.null(left_nobs <- .subset2(left_coords, "nobs"))) {
        cli_abort("layout coordinates for {.arg {arg1}} must be initialized",
            call = call
        )
    }
    # if the right layout is a `cross_layout()`, we get the left coords
    right_coords <- get_layout_coords(right,
        direction = direction, hand = "left"
    )
    if (is.null(right_coords)) {
        cli_abort(c(
            sprintf("{.arg {arg2}} cannot be a %s", object_name(right)),
            i = "{.arg {arg2}} must align observations in {.field {direction}}."
        ), call = call)
    } else if (is.null(right_nobs <- .subset2(right_coords, "nobs"))) {
        cli_abort("layout coordinates for {.arg {arg2}} must be initialized",
            call = call
        )
    }
    # both layout must have same `nobs`
    if (!identical(left_nobs, right_nobs)) {
        cli_abort(
            "{.arg {arg1}} (nobs: {left_nobs}) and {.arg {arg2}} (nobs: {right_nobs}) are incompatible",
            call = call
        )
    }
    # both layout must have compatible `panel`
    left_panel <- .subset2(left_coords, "panel")
    right_panel <- .subset2(right_coords, "panel")
    if (is.null(left_panel) && is.null(right_panel)) {

    } else if (!is.null(left_panel) && !is.null(right_panel)) {
        # they can have different labels, but the underlying value must be the
        # same
        if (!identical(as.integer(left_panel), as.integer(right_panel))) {
            cli_abort(
                "{.arg {arg1}} and {.arg {arg2}} have incompatible panel groups",
                call = call
            )
        }
    } else if (is.null(left_panel)) {
        left_coords$panel <- right_panel
        # we always make the index following the panel
        if (!is.null(old_index <- .subset2(left_coords, "index"))) {
            # we always prevent from reordering twice.
            if (!all(old_index == reorder_index(right_panel, old_index))) {
                cli_abort(sprintf(
                    "%s disrupt the previously established ordering index of %s",
                    "{.arg {arg2}}", "{.arg {arg1}}"
                ), call = call)
            }
        }
        left <- update_layout_coords(left,
            direction = direction, coords = left_coords
        )
    } else {
        right_coords$panel <- left_panel
        # we always make the index following the panel
        if (!is.null(old_index <- .subset2(right_coords, "index"))) {
            # we always prevent from reordering twice.
            if (!all(old_index == reorder_index(left_panel, old_index))) {
                cli_abort(sprintf(
                    "%s disrupt the previously established ordering index of %s",
                    "{.arg {arg1}}", "{.arg {arg2}}"
                ), call = call)
            }
        }
        right <- update_layout_coords(right,
            direction = direction, coords = right_coords
        )
    }
    size <- check_size(size, call = call)
    sizes <- check_stack_sizes(sizes, call = call)
    methods::new(
        "CrossLayout",
        left = left, right = right,
        direction = direction,
        plot = ggplot(mapping = mapping),
        size = size,
        sizes = sizes,
        theme = theme
    )
}

#' Connect two layouts crosswise
#'
#' @param e1,e2 A `r rd_layout()`.
#' @param ... Not used currently.
#' @inheritParams stack_align
#' @param size A single numeric value or a [`unit`][grid::unit] object
#' specifying the relative width (when `direction = "horizontal"`) or height
#' (when `direction = "vertical"`) of the main plot.
#' @inheritParams quad_free
#' @rdname cross_link
#' @export
methods::setGeneric("cross_link",
    function(e1, e2, direction = NULL, ..., mapping = aes(),
             size = NA, theme = NULL, sizes = NA) {
        standardGeneric("cross_link")
    },
    signature = c("e1", "e2")
)

cross_link <- function() structure(list(), class = "ggalign_cross_link")

#' @include layout-stack-.R
#' @rdname cross_link
methods::setMethod(
    "cross_link", c("StackLayout", "StackLayout"),
    function(e1, e2, direction = NULL, ...) {
        direction1 <- e1@direction
        direction2 <- e2@direction
        if (is.null(direction)) {
            if (!identical(direction1, direction2)) {
                cli_abort(
                    "{.arg e1} and {.arg e2} must have the same direction"
                )
            }
            direction <- direction1
        } else {
            direction <- match.arg(direction, c("horizontal", "vertical"))
            if (!identical(direction, direction1)) {
                cli_abort(c(
                    "{.arg e1} is not compatible with the layout",
                    i = "Only {.field {direction}} stack is allowed"
                ))
            } else if (!identical(direction, direction2)) {
                cli_abort(c(
                    "{.arg e2} is not compatible with the layout",
                    i = "Only {.field {direction}} stack is allowed"
                ))
            }
        }
        new_cross_layout(left = e1, right = e2, direction = direction, ...)
    }
)

#' @include layout-quad-.R
#' @include layout-stack-.R
#' @rdname cross_link
methods::setMethod(
    "cross_link", c("QuadLayout", "StackLayout"),
    function(e1, e2, direction = NULL, ...) {
        direction2 <- e2@direction
        if (is.null(direction)) {
            direction <- direction2
        } else {
            direction <- match.arg(direction, c("horizontal", "vertical"))
            if (!identical(direction, direction2)) {
                cli_abort(c(
                    "{.arg e2} is not compatible with the layout",
                    i = "Only {.field {direction}} stack is allowed"
                ))
            }
        }
        new_cross_layout(left = e1, right = e2, direction = direction, ...)
    }
)

#' @include layout-quad-.R
#' @include layout-stack-.R
#' @rdname cross_link
methods::setMethod(
    "cross_link", c("StackLayout", "QuadLayout"),
    function(e1, e2, direction = NULL, ...) {
        direction1 <- e1@direction
        if (is.null(direction)) {
            direction <- direction1
        } else {
            direction <- match.arg(direction, c("horizontal", "vertical"))
            if (!identical(direction, direction1)) {
                cli_abort(c(
                    "{.arg e1} is not compatible with the layout",
                    i = "Only {.field {direction}} stack is allowed"
                ))
            }
        }
        new_cross_layout(left = e1, right = e2, direction = direction, ...)
    }
)

@Yunuuuu
Copy link
Owner Author

Yunuuuu commented Dec 1, 2024

  1. add a special function, which can be added to stack_layout(), which will reset the index.

@Yunuuuu
Copy link
Owner Author

Yunuuuu commented Dec 1, 2024

try to fix #14

@Yunuuuu Yunuuuu merged commit 50db033 into main Dec 1, 2024
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant