R/pretty_plot.R

Defines functions pretty_plot

Documented in pretty_plot

#' @title Prettier default plots
#' @description \code{\link[prettyGraphics]{pretty_plot}} is a general function which creates plots with pretty axes for multiple plotting functions. The default option functions like \code{\link[graphics]{plot}} but implements pretty axes using \code{\link[prettyGraphics]{pretty_axis}}. Arguments can be passed to \code{\link[graphics]{plot}}, but additional arguments (e.g., \code{points_args}, \code{lines_args} and \code{mtext_args}, which can be used to add points, lines and control axis labels respectively) provide additional flexibility if required. Some other plotting functions can be implemented by adjusting the plotting function, \code{f}, and by specifying whether that plot depends on \code{x}, \code{y} or \code{x} and \code{y}. The function can return the list from \code{\link[prettyGraphics]{pretty_axis}} which can be useful for later adjustments to plots (e.g. setting the clipping region with \code{\link[graphics]{clip}}).
#'
#' @param x The x coordinates or an object from which coordinates can be extracted (see Details). Coordinates are used to create the axes and may be plotted (see \code{f} and \code{plot_coordinates}).
#' @param y (optional) The y coordinates.
#' @param f A function used to create a plot. The default is \code{\link[graphics]{plot}} but some other functions are supported. \code{f} should support the following arguments: \code{axes}, which are turned off automatically; \code{xlim}, \code{ylim}; \code{xlab}; \code{ylab}; and \code{main}. This feature is experimental.
#' @param plot_xy A character vector specifying whether the plotting function, \code{f}, requires \code{"x"}, \code{"y"} or both (\code{"xy"}).
#' @param plot_coordinates A logical input which defines whether or not the plotting function acts on coordinates extracted from \code{x} or the original object (see examples using \code{\link[raster]{image}}).
#' @param pretty_axis_args A named list or arguments passed to \code{\link[prettyGraphics]{pretty_axis}} to define pretty axes. For convenience, x and y axis limits can also be specified via \code{xlim} and \code{ylim} (see below).
#' @param xlim,ylim A vector of two axis limits. This is a short-cut to specifying axis limits via \code{pretty_axis_args} (see \code{\link[prettyGraphics]{pretty_axis}}).
#' @param points_args A named list of arguments passed to \code{\link[graphics]{points}} to control points. These can also be supplied via \code{...} but \code{point_args} can provide additional flexibility where required. To implement this option, you probably want to specify \code{type = "n"} via \code{...} because \code{points_args} is evaluated after the plot is evaluated.
#' @param lines_args A named list of arguments passed to \code{\link[graphics]{lines}} to control lines. These can also be supplied via \code{...} but \code{lines_args} can provide additional flexibility where required. To implement this option, you probably want to specify \code{type = "n"} via \code{...} because \code{lines_args} is evaluated after the plot is evaluated.
#' @param xlab A character input which defines the label for the x axis. By default, this is "" so that labels can be added via \code{mtext_args} which is more flexible (see below). However, the label can be specified via \code{xlab} for speed.
#' @param ylab A character input which defines the label for the y axis. By default, this is "" so that labels can be added via \code{mtext_args} which is more flexible (see below). However, the label can be specified via \code{ylab} for speed.
#' @param main A character input which defines the label for the plot axis. By default, this is "" so that labels can be added via \code{mtext_args} which is more flexible (see below). However, the label can be specified via \code{main} for speed.
#' @param mtext_args A named list of arguments passed to \code{\link[graphics]{mtext}} to control axes' labels. These can also be supplied via \code{xlab}, \code{ylab} and \code{main} but \code{mtext_args} can provide additional flexibility where required.
#' @param return_list (depreciated) A logical input which defines whether or not to return the list produced by \code{\link[prettyGraphics]{pretty_axis}}.
#' @param ... Other parameters passed to \code{f}.
#'
#' @details \code{x} and \code{y} coordinates usually need to be provided. Some other object classes may be provided to \code{x}, from which x and y coordinates can be extracted to create axes. In this case, the user needs to indicate whether the plotting function, \code{f}, requires \code{x} and/or \code{y} and acts on extracted coordinates (\code{plot_coordinates = TRUE}) or the original object (\code{plot_coordinates = FALSE}). Objects of class density and some Spatial* objects (RasterLayer, SpatialPoints, Line, Lines, Polygon, Polygons, SpatialPolygonsDataFrame) are currently supported. If \code{plot_xy = "xy"} and only \code{x} is provided, \code{x} is treated as the response variable and plotted against an index (like \code{\link[graphics]{plot}}).
#'
#' @return The function returns a plot and, invisibly, a list of arguments that are used to create pretty axes via \code{\link[prettyGraphics]{pretty_axis}}.
#'
#' @examples
#' #### Example (1): An example with graphics::plot()
#' set.seed(1)
#' pretty_plot(stats::runif(10, 0, 1),
#'             stats::runif(10, 0, 1),
#'             points_args = list(pch = 21, col = scales::alpha("black", 0.8)))
#'
#' #### Example (2): pretty_plot() can also work with factors.
#' # ... As usual, the number of breaks can be controlled via pretty_axis_args:
#' ## Define data:
#' dx <- factor(LETTERS[1:10])
#' dy <- 1:10
#' ## Example plots:
#' pp <- par(mfrow = c(2, 2))
#' # Specify a break for every factor level via the pretty 'n' argument.
#' # ... (this can also be achieved via the units argument)
#' pretty_plot(dx, dy,
#'             pretty_axis_args = list(side = 1:2, pretty = list(n = 10))
#' )
#' # Specify a break for every other factor level via the pretty 'n' argument.
#' # ... (this can also be achieved via the units argument)
#' pretty_plot(dx, dy,
#'             pretty_axis_args = list(side = 1:2, pretty = list(n = 10/2))
#' )
#' # Comparisons to default plots:
#' graphics::plot(dx, dy)
#' graphics::plot.default(dx, dy)
#' par(pp)
#'
#' #### Example (3): If only x is provided, x is plotted against an index
#' pretty_plot(x = c(10, 20))
#'
#' #### Example (4): Coordinates usually need to be provided
#' # ... but pretty_plot() works with some other objects.
#' ## Density example
#' pretty_plot(density(stats::rnorm(100, 0, 1)), type = "l")
#' ## RasterLayer example
#' # Define example raster
#' r <- raster::raster(nrows=10, ncols=10)
#' r <- raster::setValues(r, 1:raster::ncell(r))
#' # Note the use of raster::image() rather than raster::plot() for plotting
#' # ... because raster::plot() doesn't act on xlim or ylim arguments defined by pretty_axis().
#' pretty_plot(x = r, y = NULL,
#'             f = raster::image, plot_xy = "x", plot_coordinates = FALSE)
#' ## SpatialPolygonsDataFrame example
#' # Outline
#' pretty_plot(x = dat_coast_around_oban, y = NULL,
#'             f = raster::plot, plot_xy = "x", plot_coordinates = FALSE)
#' # Customised plot
#' pretty_plot(x = dat_coast_around_oban, y = NULL,
#'             pretty_axis_args = list(side = 1:4),
#'             xlim = raster::extent(dat_coast_around_oban)[1:2],
#'             ylim = raster::extent(dat_coast_around_oban)[3:4],
#'             f = raster::plot, plot_xy = "x", plot_coordinates = FALSE,
#'             col = "darkgreen")
#'
#' #### Example (5): An example with stats::qqnorm()
#' # Define x and y values for qqnorm plot
#' set.seed(1)
#' qq <- qqplot(stats::rnorm(100, 0, 1), stats::rnorm(100, 0, 1), plot.it = FALSE)
#' # Define plot, saving list of axis parameters in axis_ls
#' # Supply x and y (qq$x and qq$y) respectively to create pretty axis limits;
#' # ... but use qqnorm to create plot which only required y (dd$y) (see ?stats::qqnorm)
#' axis_ls <- pretty_plot(qq$x, qq$y, f = stats::qqnorm, plot_xy = "y")
#' # Set clipping region with axis limits
#' usr <- par("usr")
#' clip(axis_ls[[1]]$lim[1], axis_ls[[1]]$lim[2], axis_ls[[2]]$lim[1], axis_ls[[2]]$lim[2])
#' # Add qqline within limits
#' qqline(qq$y)
#' do.call("clip", as.list(usr))
#'
#' @author Edward Lavender
#' @export
#'

#######################################
#######################################
#### pretty_plot

pretty_plot <-
  function(x,
           y = NULL,
           f = graphics::plot,
           plot_xy = "xy",
           plot_coordinates = TRUE,
           pretty_axis_args = list(side = 1:2, pretty = list(n = 5)),
           xlim = NULL, ylim = NULL,
           points_args = list(),
           lines_args = list(),
           xlab, ylab, main,
           mtext_args = list(),
           return_list = NULL,...){

    #### Initial checks
    if(is.null(x)) stop("'x' is NULL.")
    if(!any(class(x) %in% c("numeric", "integer", "factor", "character", "Date", "POSIXct",
                            "density", "RasterLayer", "SpatialPoints", "Line", "Lines", "Polygon", "Polygons", "SpatialPolygonsDataFrame"))){
      message("class(x) may not be supported.")
    }
    check_input_value(arg = "plot_xy", input = plot_xy, supp = c("x", "y", "xy"), default = "xy")
    check...("axes",...)

    #### Axis titles (before objects are re-defined)
    x_depar <- deparse(substitute(x))
    if(missing(xlab)) {
      xlab_missing <- TRUE
      xlab <- x_depar
    } else xlab_missing <- FALSE
    if(missing(ylab)) {
      ylab_missing <- TRUE
      ylab <- deparse(substitute(y))
    } else ylab_missing <- FALSE
    if(missing(main)) main <- ""

    #### Object inheritance for pretty_axis()
    # Extract coordinates
    xy <- pull_xy(x, y)
    # Replace labels, if necessary (suitable for some object types)
    if(xlab_missing & !is.null(xy$xlab)) xlab <- xy$xlab
    if(ylab_missing & !is.null(xy$ylab)) ylab <- xy$ylab
    # Extract coordinates for plotting from list
    if(plot_coordinates){
      x <- xy$x
      y <- xy$y
    }

    #### If y isn't supplied, plot x against an index like graphics::plot()
    if(plot_xy == "xy" & is.null(xy$y)){
      message("'y' argument not supplied; 'x' is plotted against an index.")
      index <- 1:length(x)
      y <- xy$x
      x <- index
      if(xlab_missing) xlab <- "Index"
      if(ylab_missing) ylab <- x_depar
      # Redefine xy list for implement_pretty_axis_args, below.
      xy <- list(x = index, y = xy$x)
    }

    #### Implement pretty_axis_args
    axis_ls <- implement_pretty_axis_args(x = list(xy$x, xy$y),
                                          pretty_axis_args = pretty_axis_args,
                                          xlim = xlim,
                                          ylim = ylim,...)

    #### Variable type updates
    # Convert factors/characters to numbers for plotting
    if(is.factor(x) | is.character(x)){
      # warning("'x' converted to a number for plotting in pretty_plot().")
      x <- as.integer(factor(x))
    }
    if(is.factor(y) | is.character(y)){
      # warning("'y' converted to a number for plotting in pretty_plot().")
      y <- as.integer(factor(y))
    }

    #### Plot the graph
    if(plot_xy == "xy"){
      f(x, y,
        axes = FALSE,
        xlab = xlab, ylab = ylab, main = main,
        xlim = axis_ls[[1]]$lim,
        ylim = axis_ls[[2]]$lim,...
      )
    } else if(plot_xy == "x"){
      f(x,
        axes = FALSE,
        xlab = xlab, ylab = ylab, main = main,
        xlim = axis_ls[[1]]$lim,
        ylim = axis_ls[[2]]$lim,...
      )
    } else if(plot_xy == "y"){
      f(y,
        axes = FALSE,
        xlab = xlab, ylab = ylab, main = main,
        xlim = axis_ls[[1]]$lim,
        ylim = axis_ls[[2]]$lim,...
      )
    }

    #### Add points
    if(length(points_args) > 0){
      points_args$x <- x
      points_args$y <- y
      do.call(graphics::points, points_args)
    }

    #### Add lines
    if(length(lines_args) > 0){
      lines_args$x <- x
      lines_args$y <- y
      do.call(graphics::lines, lines_args)
    }

    #### Add pretty axis
    if(is.null(pretty_axis_args$add)) pretty_axis_args$add <- TRUE
    if(pretty_axis_args$add) pretty_axis(axis_ls = axis_ls, add = TRUE)

    #### Add axes labelling
    implement_mtext_args(mtext_args)

    #### Return list
    if(!is.null(return_list)) warning("The 'return_list' argument is depreciated.")
    return(invisible(axis_ls))

  }



#### End of code.
#######################################
#######################################
edwardlavender/prettyGraphics documentation built on Jan. 19, 2025, 2:47 p.m.