R/map_ggplot.R

Defines functions geom_acpoint acpoint_grob drawDetails.acpoint postDrawDetails.acpoint preDrawDetails.acpoint ggplot.acmap

Documented in ggplot.acmap

#' Plot an antigenic map using ggplot
#'
#' Method for plotting an antigenic map as a ggplot object
#'
#' @param data The acmap to plot
#' @param mapping Default list of aesthetic mappings to use for plot, not currently used
#' @param optimization_number The optimization number to plot
#' @param xlim optional x axis limits
#' @param ylim optional y axis limits
#' @param plot_ags logical, should antigens be plotted
#' @param plot_sr logical, should antigens be plotted
# #' @param plot_labels should point labels be plotted, can be true, false or
# #'   "antigens" or "sera"
#' @param plot_blobs logical, should stress blobs be plotted if present
#' @param plot_hemisphering logical, should hemisphering points be indicated, if
#'   tested for already with `checkHemisphering()` (and if present)
#' @param show_procrustes logical, should procrustes lines be shown, if present
#' @param show_error_lines logical, should error lines be drawn
#' @param plot_stress logical, should map stress be plotted in lower left corner
#' @param indicate_outliers how should points outside the plotting region be
#'   indicated, either FALSE, for not shown, or "arrowheads" for small arrowheads
#'   like in the viewer.
#' @param grid.col grid line color
#' @param grid.lwd grid line width
#' @param grid.margin.col grid margin color
#' @param grid.margin.lwd grid margin line width
# #' @param outlier.arrow.col outlier arrow color
#' @param fill.alpha alpha for point fill
#' @param outline.alpha alpha for point outline
# #' @param label.offset amount by which any point labels should be offset from
# #'   point coordinates in fractions of a character width
#' @param padding padding at limits of the antigenic map, ignored if xlim or
#'   ylim set explicitly
#' @param arrow_angle angle of arrow heads drawn for procrustes lines
#' @param arrow_length length of arrow heads drawn for procrustes lines in cm
#' @param margins margins in inches for the plot
#' @param ... additional arguments, not used
#' @param environment not used
#'
#' @returns Returns the ggplot plot
#'
#' @family functions to view maps
#' @export
#'
ggplot.acmap <- function(
  data = NULL,
  mapping = NULL,
  optimization_number = 1,
  xlim = NULL,
  ylim = NULL,
  plot_ags = TRUE,
  plot_sr  = TRUE,
  # plot_labels = FALSE,
  plot_blobs = TRUE,
  plot_hemisphering = TRUE,
  show_procrustes = TRUE,
  show_error_lines = FALSE,
  plot_stress = FALSE,
  indicate_outliers = "arrowheads",
  grid.col = "grey90",
  grid.lwd = 0.5,
  grid.margin.col = "grey50",
  grid.margin.lwd = grid.lwd,
  # outlier.arrow.col = grid.col,
  fill.alpha    = 0.8,
  outline.alpha = 0.8,
  # label.offset = 0,
  padding = 1,
  arrow_angle = 25,
  arrow_length = 0.2,
  margins = rep(0.5, 4),
  ...,
  environment = NULL
  ) {

  # Set parameters
  map <- data

  # Do dimension checks
  if (mapDimensions(map, optimization_number) != 2) {
    stop("Plotting is only supported for 2D maps, please try view()")
  }
  if (optimization_number != 1 && plot_blobs) {
    warning("Optimization number ignored when plotting blobs")
  }

  # Set plot lims
  lims <- mapPlotLims(map, optimization_num = optimization_number, padding = padding)
  if (is.null(xlim)) xlim <- lims$xlim
  if (is.null(ylim)) ylim <- lims$ylim

  # Set point visibility
  if (!plot_ags) agShown(map) <- FALSE
  if (!plot_sr)  srShown(map) <- FALSE

  # Do the plot
  plotdata <- tibble::tibble(
    x = c(agCoords(map, optimization_number)[,1], srCoords(map, optimization_number)[,1]),
    y = c(agCoords(map, optimization_number)[,2], srCoords(map, optimization_number)[,2]),
    type = c(rep("AG", numAntigens(map)), rep("SR", numSera(map))),
    fill = c(agFill(map), srFill(map)),
    outline = c(agOutline(map), srOutline(map)),
    outline_width = c(agOutlineWidth(map), srOutlineWidth(map)),
    shape = c(agShape(map), srShape(map)),
    size = c(agSize(map), srSize(map)),
    rotation = c(agRotation(map), srRotation(map)),
    aspect = c(agAspect(map), srAspect(map)),
    shown = c(agShown(map), srShown(map)),
    text = c(agNames(map), srNames(map))
  )

  ## Adjust alpha
  if (!is.null(fill.alpha)) plotdata$fill <- grDevices::adjustcolor(plotdata$fill, alpha.f = fill.alpha)
  if (!is.null(outline.alpha)) plotdata$outline <- grDevices::adjustcolor(plotdata$outline, alpha.f = outline.alpha)

  # Add blob data
  plotdata$blob <- lapply(seq_len(numPoints(map)), function(x) NULL)
  if (plot_blobs && hasTriangulationBlobs(map)) plotdata$blob <- ptTriangulationBlobs(map, optimization_number)
  if (plot_blobs && hasBootstrapBlobs(map)) plotdata$blob <- ptBootstrapBlobs(map, optimization_number)

  ## Fade out points not included in procrustes
  if (hasProcrustes(map, optimization_number) && !isFALSE(show_procrustes)) {

    pc_data <- ptProcrustes(map, optimization_number)
    pc_coords <- rbind(pc_data$ag_coords, pc_data$sr_coords)
    pc_coords_na <- is.na(pc_coords[,1])

    # Fade out points with NA procrustes coords
    if (sum(pc_coords_na) > 0) {
      plotdata$fill[pc_coords_na] <- grDevices::adjustcolor(plotdata$fill[pc_coords_na], alpha.f = 0.2)
      plotdata$outline[pc_coords_na] <- grDevices::adjustcolor(plotdata$outline[pc_coords_na], alpha.f = 0.2)
    }

  }

  # Do the ggplot
  gp <- plotdata %>%
    dplyr::slice(ptDrawingOrder(map)) %>%
    dplyr::filter(.data$shown) %>%
    ggplot2::ggplot() +
    geom_acpoint(
      mapping = ggplot2::aes(
        x = .data$x,
        y = .data$y,
        color = .data$outline,
        fill = .data$fill,
        shape = .data$shape,
        size = .data$size,
        rotation = .data$rotation,
        aspect = .data$aspect,
        blob = .data$blob,
        stroke = .data$outline_width,
        text = .data$text
      ),
      indicate_outliers = indicate_outliers
    ) +
    ggplot2::scale_fill_identity() +
    ggplot2::scale_color_identity() +
    ggplot2::scale_size_identity() +
    ggplot2::scale_shape_identity() +
    ggplot2::scale_x_continuous(
      breaks = function(x) seq(from = x[1], to = x[2], by = 1)
    ) +
    ggplot2::scale_y_continuous(
      breaks = function(x) seq(from = x[1], to = x[2], by = 1)
    ) +
    ggplot2::coord_fixed(
      expand = FALSE,
      xlim = xlim,
      ylim = ylim
    ) +
    ggplot2::theme(
      panel.border = ggplot2::element_rect(
        colour = grid.margin.col,
        linewidth = grid.margin.lwd,
        fill = NA
      ),
      panel.background = ggplot2::element_rect(
        fill = "white"
      ),
      panel.grid.minor = ggplot2::element_blank(),
      panel.grid.major = ggplot2::element_line(
        color = grid.col,
        size = grid.lwd
      ),
      axis.text = ggplot2::element_blank(),
      axis.ticks = ggplot2::element_blank(),
      axis.line = ggplot2::element_blank(),
      axis.title = ggplot2::element_blank(),
      plot.margin = ggplot2::margin(
        b = margins[1],
        l = margins[2],
        t = margins[3],
        r = margins[4],
        unit = "lines"
      )
    ) +
    ggplot2::labs(
      x = "",
      y = ""
    )

  ## Plot error lines
  if (show_error_lines) {

    # Fetch error lines data
    error_lines <- ac_errorline_data(keepSingleOptimization(map, optimization_number))

    # Add the error lines annotation
    gp <- gp + ggplot2::annotate(
      "segment",
      x = error_lines$x,
      y = error_lines$y,
      xend = error_lines$xend,
      yend = error_lines$yend,
      color = ifelse(error_lines$color == 0, "blue", "red")
    )

  }

  ## Plot hemisphering
  if (plot_hemisphering && hasHemisphering(map, optimization_number)) {

    # Add hemisphering data
    plotdata$hemisphering <- ptHemisphering(map, optimization_number)

    # Show hemisphering points
    for (i in which(vapply(plotdata$hemisphering, length, numeric(1)) > 0)) {
      for (hemi in plotdata$hemisphering[[i]]) {

        # Set style based on diagnosis
        if (hemi$diagnosis == "hemisphering") {
          arrowends <- "both"
          arrowcol  <- "black"
        }
        if (hemi$diagnosis == "trapped") {
          arrowends <- "last"
          arrowcol  <- "red"
        }
        if (hemi$diagnosis == "hemisphering-trapped") {
          arrowends <- "both"
          arrowcol  <- "red"
        }

        gp <- gp + ggplot2::annotate(
          "segment",
          x = plotdata$x[i],
          y = plotdata$y[i],
          xend = hemi$coords[1],
          yend = hemi$coords[2],
          arrow = ggplot2::arrow(
            ends = arrowends,
            type = "closed",
            angle = 18,
            length = grid::unit(0.3, "cm")
          ),
          linewidth = 1,
          color = arrowcol
        )

      }
    }
  }

  # Add procrustes
  if (hasProcrustes(map, optimization_number) && !isFALSE(show_procrustes)) {

    pc_data <- ptProcrustes(map, optimization_number)
    pc_coords <- rbind(pc_data$ag_coords, pc_data$sr_coords)
    pc_coords <- applyMapTransform(pc_coords, map, optimization_number)
    pt_coords <- ptCoords(map, optimization_number)

    arrowdata <- do.call(
      dplyr::bind_rows,
      lapply(seq_len(numPoints(map)), function(i){
        tibble::tibble(
          x0 = pt_coords[i, 1],
          y0 = pt_coords[i, 2],
          x1 = pc_coords[i, 1],
          y1 = pc_coords[i, 2]
        )
      })
    ) %>%
    dplyr::filter(
      !is.na(.data$x1)
    )

    gp <- gp + ggplot2::annotate(
      "segment",
      x = arrowdata$x0,
      y = arrowdata$y0,
      xend = arrowdata$x1,
      yend = arrowdata$y1,
      arrow = ggplot2::arrow(
        type = "closed",
        angle = arrow_angle,
        length = grid::unit(arrow_length, "cm")
      ),
      lineend = "butt",
      linejoin = "mitre",
      linewidth = 1
    )

  }

  # Annotate stress
  if (plot_stress) {
    gp <- gp + ggplot2::annotate(
      "text",
      x = xlim[1] + diff(range(xlim))*0.01,
      y = ylim[1] + diff(range(ylim))*0.02,
      label = round(mapStress(map, optimization_number), 2),
      vjust = "inward",
      hjust = "inward",
      family = "mono"
    )
  }

  # Return the plot
  gp

}


angles <- seq(from = -pi, to = pi, length.out = 100)
shapes <- list(
  triangle = list(
    x = c(0.0669873, 0.9330127, 0.5),
    y = c(0.25, 0.25, 1.0)
  ),
  egg = list(
    x = 0.4 * cos(angles / 4) * sin(angles) + 0.5,
    y = -0.5 * cos(angles) + 0.5
  ),
  uglyegg = list(
    x = c(0.5, 0.05, 0.15, 0.50, 0.85, 0.95),
    y = c(0, 0.2, 0.8, 1, 0.8, 0.2)
  )
)

#' @export
#' @noRd
preDrawDetails.acpoint <- function(x){
  if (x$shape != "blob") {
    grid::pushViewport(
      grid::viewport(
        x=x$x,
        y=x$y,
        height = grid::unit(2*x$size, "pt"),
        width = grid::unit(2*x$size*x$aspect, "pt"),
        angle = -180 * x$rotation / pi,
        default.units = "native"
      )
    )
  }
}

#' @export
#' @noRd
postDrawDetails.acpoint <- function(x){
  if (x$shape != "blob") {
    grid::upViewport()
  }
}

#' @export
#' @noRd
drawDetails.acpoint <- function(x, recording=FALSE, ...){
  switch(
    x$shape,
    circle = grid::grid.circle(
      x = 0.5,
      y = 0.5,
      r = 0.5
    ),
    box = grid::grid.rect(
      x = 0.5,
      y = 0.5,
      width = 0.8862,
      height = 0.8862
    ),
    triangle = grid::grid.polygon(
      x = c(0.0669873, 0.9330127, 0.5),
      y = c(0.25, 0.25, 1.0)
    ),
    egg = grid::grid.polygon(
      x = 0.4 * cos(angles / 4) * sin(angles) + 0.5,
      y = -0.5 * cos(angles) + 0.5
    ),
    uglyegg = grid::grid.polygon(
      x = c(0.5, 0.05, 0.15, 0.50, 0.85, 0.95),
      y = c(0, 0.2, 0.8, 1, 0.8, 0.2)
    ),
    outlier = grid::grid.polygon(
      x = c(1, 1, 0.5),
      y = c(0.25, 0.75, 0.5)
    ),
    blob = do.call(grid::grobTree, lapply(x$blob, function(blob) {
      grid::grid.polygon(
        x = blob$x,
        y = blob$y
      )
    }))
  )
}

acpoint_grob <- function(x=0.5, y=0.5, gp, ...){
  grid::grob(x=x, y=y, cl="acpoint", gp = gp, ...)
}

draw_key_acpoint <- function (data, params, size) {
  data$shape <- 19
  grid::pointsGrob(
    0.5, 0.5,
    pch = data$shape,
    gp = grid::gpar(
      col = data$colour,
      fill = data$fill,
      fontsize = 10,
      lwd = 1
    )
  )
}

GeomAcPoint <- ggplot2::ggproto(
  "GeomAcPoint",
  ggplot2::Geom,
  required_aes = c("x", "y"),
  default_aes = ggplot2::aes(
    shape = "circle",
    colour = "black",
    fill = "black",
    stroke = 1,
    rotation = 0,
    aspect = 1,
    indicate_outliers = FALSE,
    size = 1,
    blob = list(),
    text = ""
  ),
  draw_key = draw_key_acpoint,
  draw_panel = function(data, panel_params, coord) {

    coords <- coord$transform(data, panel_params)
    coords$shape <- tolower(coords$shape)
    coords$shape[coords$shape == "e"] <- "egg"
    coords$shape[coords$shape == "u"] <- "uglyegg"
    coords$shape[coords$shape == "b"] <- "box"
    coords$shape[coords$shape == "c"] <- "circle"
    coords$shape[coords$shape == "t"] <- "triangle"

    polys <- lapply(split(coords, seq_len(nrow(coords))), function(row) {

      # Set parameters
      shape <- row$shape
      rotation <- row$rotation
      ptcoord <- c(row$x, row$y)

      # Rescale blobs
      blob <- row$blob[[1]]
      if (!is.null(blob)) {

        shape <- "blob"
        blob <- lapply(blob, function(b) {
          b$x <- panel_params$x$rescale(b$x)
          b$y <- panel_params$y$rescale(b$y)
          b
        })

      } else {

        if (row$indicate_outliers == "arrowheads") {
          if (min(ptcoord) < 0 || max(ptcoord) > 1) {
            shape <- "outlier"
            ptcoord[1] <- clamp(ptcoord[1], 0, 1)
            ptcoord[2] <- clamp(ptcoord[2], 0, 1)
            rotation <- atan((row$y - ptcoord[2]) / (row$x - ptcoord[1]))
          }
        }

      }

      # Make point
      acpoint_grob(
        x = ptcoord[1],
        y = ptcoord[2],
        gp = grid::gpar(
          col = row$colour,
          fill = row$fill,
          lwd = row$stroke
        ),
        size = row$size,
        rotation = rotation,
        aspect = row$aspect,
        shape = shape,
        blob = blob
      )

    })

    do.call(grid::grobTree, polys)

  }
)

geom_acpoint <- function(
  mapping = NULL,
  data = NULL,
  stat = "identity",
  position = "identity",
  na.rm = FALSE,
  show.legend = NA,
  inherit.aes = TRUE,
  ...
) {

  ggplot2::layer(
    geom = GeomAcPoint,
    mapping = mapping,
    data = data,
    stat = stat,
    position = position,
    show.legend = show.legend,
    inherit.aes = inherit.aes,
    params = list(na.rm = na.rm, ...)
  )

}


# ## Plot points in batches, stopping when you reach a special point
# ## that needs to be plotted with polygon
# gpoints <- list()
# pt_plot_batch <- c()
#
# for (i in plotted_pt_order) {
#
#   # Check if it is a non-standard shape, plot separately if so
#   if (
#     !pts$shape[i] %in% c("CIRCLE", "BOX", "TRIANGLE")
#     || pts$rotation[i] != 0
#     || pts$aspect[i] != 1
#     ) {
#
#     # Plot last batch of points
#     if (length(pt_plot_batch) > 0) {
#       gpoints <- c(gpoints, list(plot_points(pts, pt_plot_batch)))
#     }
#
#     # Clear point plot record
#     pt_plot_batch <- c()
#
#     # Do a special plot for the special shape
#     gpoints <- c(gpoints, list(
#       plot_special_point(
#         coord        = pts$coords[i, , drop = F],
#         shape        = pts$shape[i],
#         fill         = pts$fill[i],
#         outline      = pts$outline[i],
#         size         = pts$size[i] * cex * 0.2,
#         outlinewidth = pts$outline_width[i],
#         rotation     = pts$rotation[i],
#         aspect       = pts$aspect[i]
#       )
#     ))
#
#   } else {
#
#     # Add to next batch of plotted points
#     pt_plot_batch <- c(pt_plot_batch, i)
#
#   }
# }
#
# # Plot remaining points
# if (length(pt_plot_batch) > 0) {
#   gpoints <- c(gpoints, list(plot_points(pts, pt_plot_batch)))
# }

Try the Racmacs package in your browser

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

Racmacs documentation built on June 22, 2024, 11:33 a.m.