R/position-brain.R

Defines functions default_order get_sep get_box stack_grid stack_vertical stack_horizontal gather_geometry split_data frame_2_position position_formula position_brain reposition_brain

Documented in position_brain reposition_brain

# position ----

#' Reposition brain slices
#'
#' Function for repositioning
#' pre-joined atlas data (i.e. data and atlas
#' already joined to a single data frame).
#' This makes it possible for users to
#' reposition the geometry data for the atlas
#' for control over final plot layout. For even
#' more detailed control over the positioning,
#' the "hemi" and "side" columns should be
#' converted into factors and ordered by wanted
#' order of appearance.
#'
#' @param data sf-data.frame of joined brain atlas and data
#' @param position position formula for slices
#'
#' @return sf-data.frame with repositioned slices
#' @export
#'
#' @examples
#' reposition_brain(dk, hemi ~ side)
#' reposition_brain(dk, side ~ hemi)
#' reposition_brain(dk, hemi + side ~ .)
#' reposition_brain(dk, . ~ hemi + side)
reposition_brain <- function(data, position = "horizontal") {
  data <- as.data.frame(data, stringsAsFactors = FALSE)
  frame_2_position(data, position)
}


#' Alter brain atlas position
#'
#' Function to be used in the position argument in geom_brain
#' to alter the position of the brain slice/views.
#'
#' @param position formula describing the rows ~ columns organization.
#'
#' @export
#' @return a ggproto object
#' @importFrom ggplot2 ggproto
#' @examples
#' library(ggplot2)
#' ggplot() +
#'   geom_brain(atlas = dk, aes(fill = region),
#'              position = position_brain(. ~ side + hemi ),
#'              show.legend = FALSE)
#'
#' ggplot() +
#'   geom_brain(atlas = dk, aes(fill = region),
#'              position = position_brain(side ~ hemi ),
#'              show.legend = FALSE)
position_brain <- function(position = "horizontal") {
  ggproto(NULL, PositionBrain, position = position)
}

PositionBrain <- ggplot2::ggproto(
  "PositionBrain",
  ggplot2:::Position,
  position = hemi + side ~ .,

  setup_params = function(self, data) {
    list(position = self$position)
  },

  compute_layer = function(self, data, params, layout) {
    df3 <- frame_2_position(data, params$position)
    bbx <- sf::st_bbox(df3$geometry)

    # rescale layout to reflect new coordinates
    if (is.null(layout$coord$limits$y)) {
      layout$coord$limits$y <- bbx[c(2, 4)]
    }

    # rescale layout to reflect new coordinates
    if (is.null(layout$coord$limits$x)) {
      layout$coord$limits$x <- bbx[c(1, 3)]
    }

    data <- df3

    df3
  }
)

# geometry movers ----

position_formula <- function(pos, data) {
  chosen <- all.vars(pos, unique = FALSE)
  chosen <- chosen[!grepl("\\.", chosen)]

  if (any(duplicated(chosen))) {
    stop(
      "Cannot position brain with the same data as columns and rows",
      call. = FALSE
    )
  }

  if (unique(data$type) == "cortical") {
    if (length(chosen) < 2) {
      stop(
        "position formula not correct. ",
        "Missing ",
        paste0(
          c("side", "hemi")[!c("side", "hemi") %in% chosen],
          collapse = " & "
        ),
        "",
        call. = FALSE
      )
    }
    position <- if (length(grep("\\+", pos)) > 0) {
      ifelse(grep("^\\.", pos) == 2, "columns", "rows")
    } else {
      chosen
    }
  }

  if (all(sum(grepl("\\.|~", pos)) != 2 & position %in% c("rows", "columns"))) {
    stop(
      "Formula for a single row or column must contain both a . and ~",
      call. = FALSE
    )
  }

  list(
    position = position,
    chosen = chosen
  )
}


frame_2_position <- function(data, pos) {
  dfpos <- split_data(data, pos)
  df2 <- lapply(dfpos$data, gather_geometry)
  posi <- ifelse(length(dfpos$position) > 1, "grid", dfpos$position)
  df3 <- switch(
    posi,
    rows = stack_vertical(df2),
    columns = stack_horizontal(df2),
    grid = stack_grid(df2, dfpos$position[1], dfpos$position[2])
  )

  df4 <- st_as_sf(df3$df)
  attr(sf::st_geometry(df4), "bbox") = df3$box

  df4
}

split_data <- function(data, position) {
  if (inherits(position, "formula")) {
    pos <- position_formula(position, data)
    df2 <- dplyr::group_by_at(data, pos$chosen)
    df2 <- dplyr::group_split(df2)
    pos <- pos$position
  } else {
    if (length(position) == 1) {
      if (position %in% c("horizontal", "vertical")) {
        position <- default_order(data)
      }
    }
    pos <- as.data.frame(strsplit(position, " "), stringsAsFactors = FALSE)
    if (unique(data$type) == "cortical") {
      k <- cbind(pos[2, ] %in% data$side, pos[1, ] %in% data$hemi)
      k <- sapply(1:nrow(k), function(x) sum(k[x, ]))
      pos <- pos[ifelse(k == 2, TRUE, FALSE)]

      df2 <- lapply(pos, function(x) {
        data[data$hemi == x[1] & data$side == x[2], ]
      })
    } else {
      df2 <- lapply(pos, function(x) {
        data[data$side == x, ]
      })
    }
    pos <- unique(ifelse(position == "vertical", "rows", "columns"))
  }

  return(list(data = df2, position = pos))
}

gather_geometry <- function(df) {
  bbx <- sf::st_bbox(df$geometry)
  df$geometry <- df$geometry - bbx[c("xmin", "ymin")]
  df
}

stack_horizontal <- function(df) {
  sep <- get_sep(df)

  bx <- list()
  for (k in 1:length(df)) {
    df[[k]]$geometry <- df[[k]]$geometry + c((k - 1) * sep[1], 0)
    bx[[k]] <- sf::st_bbox(df[[k]]$geometry)
  }

  list(
    df = do.call(rbind, df),
    box = get_box(bx)
  )
}

stack_vertical <- function(df) {
  sep <- get_sep(df)

  bx <- list()
  for (k in 1:length(df)) {
    df[[k]]$geometry <- df[[k]]$geometry + c(0, (k - 1) * sep[2])
    bx[[k]] <- sf::st_bbox(df[[k]]$geometry)
  }

  list(
    df = do.call(rbind, df),
    box = get_box(bx)
  )
}

stack_grid <- function(df, rows, columns) {
  bx <- list()
  sep <- get_sep(df)

  if (length(df) == 4) {
    # switch columns if they are not the same
    # so grid aligns properly
    if (unique(df[[3]][columns]) != unique(df[[2]][columns])) {
      df <- list(df[[1]], df[[2]], df[[4]], df[[3]])
    }
  }

  # move second and fourth on x
  for (k in c(2, 3)) {
    df[[k]]$geometry <- df[[k]]$geometry + c(sep[1], 0)
  }

  # move third and fourth on y
  for (k in c(3, 4)) {
    df[[k]]$geometry <- df[[k]]$geometry + c(0, sep[2])
  }

  bx <- lapply(df, function(x) sf::st_bbox(x$geometry))
  df <- do.call(rbind, df)

  df[, c("xmin", "xmax", "ymin", "ymax")] <- NULL

  list(
    df = df,
    box = get_box(bx)
  )
}

get_box <- function(bx) {
  bx <- do.call(rbind, bx)
  pad <- max(bx) * .01
  bx <- c(-pad, -pad, max(bx[, "xmax"]) + pad, max(bx[, "ymax"]) + pad)
  x <- stats::setNames(bx, c("xmin", "ymin", "xmax", "ymax"))
  class(x) <- "bbox"
  x
}

get_sep <- function(data) {
  sep <- sapply(data, function(x) sf::st_bbox(x$geometry))
  sep <- c(max(sep[3, ]), max(sep[4, ]))
  c("x" = sep[1] + sep[1] * .2, "y" = sep[2] + sep[2] * .2)
}

default_order <- function(data) {
  if (unique(data$type) == "cortical") {
    return(c("left lateral", "left medial", "right medial", "right lateral"))
  }
  unique(data$side)
}

Try the ggseg package in your browser

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

ggseg documentation built on Dec. 22, 2025, 5:06 p.m.