R/monitor_ggTimeseries.R

Defines functions monitor_ggTimeseries

Documented in monitor_ggTimeseries

#' @title Create a timeseries plot for one or more monitors
#'
#' @description
#' This function assembles various layers to create a production-ready
#' timeseries plot for one or more monitors.
#'
#' @param monitor A \emph{mts_monitor} object.
#' @param startdate Desired start date (integer or character in ymd format or
#'   POSIXct).
#' @param enddate Desired end date (integer or character in ymd format or
#'   POSIXct).
#' @param id vector of deviceDeploymentIDs to include in the plot. If more than
#'   one, different monitors will be plotted in different colors.
#' @param style Plot style. \code{small} or \code{large}. \code{style = small}
#'   is appropriate for plots 450x450px or smaller; \code{style = large} is
#'   appropriate for plots larger than 450x450px.
#' @param title Plot title. If NULL, a suitable title will be constructed.
#' @param timezone Olson timezone name for x-axis scale and date parsing. If
#'   NULL the timezone of the specified monitor will be used.
#' @param ... Arguments passed onto \code{\link{ggplot_pm25Timeseries}}.
#'
#' @return A \emph{ggplot} object.
#'
#' @import ggplot2
#' @importFrom rlang .data
#'
#' @export
#'
#' @examples
#' library(AirMonitorPlots)
#'
#' AirMonitor::NW_Megafires %>%
#'   monitor_ggTimeseries(
#'     startdate = 20150815,
#'     enddate = 20150831,
#'     id = "d01b991c53583048_160690012_03"
#'   )
#'
#' AirMonitor::Carmel_Valley %>%
#'   monitor_ggTimeseries(
#'     startdate = 20160801,
#'     enddate = 20160810
#'   )
#'

monitor_ggTimeseries <- function(
  monitor,
  startdate = NULL,
  enddate = NULL,
  id = NULL,
  style = c("small", "large"),
  title = NULL,
  timezone = NULL,
  ...
) {

  # ----- Validate Parameters --------------------------------------------------

  MazamaCoreUtils::stopIfNull(monitor)

  # Convert monitor to tidy structure
  if ( AirMonitor::monitor_isValid(monitor) ) {
    mts_tidy <- monitor_toTidy(monitor)
  } else {
    stop("monitor is not a mts_monitor object.")
  }

  # Check style
  style <- match.arg(style)

  # Check deviceDeploymentIDs
  if ( any(!id %in% unique(mts_tidy$deviceDeploymentID)) ) {
    invalidIDs <- id[which(!id %in% unique(mts_tidy$deviceDeploymentID))]
    stop(paste0(
      "Invalid ids specified. 'monitor' does not contain deviceDeploymentIDs: ",
      paste0(invalidIDs, collapse = ", ")
    ))
  }

  # Determine the timezone (code borrowed from custom_pm25TimeseriesScales.R)
  if ( is.null(timezone) ) {
    if ( length(unique(mts_tidy$timezone)) > 1 ) {
      timezone <- "UTC"
    } else {
      timezone <- mts_tidy$timezone[1]
    }
  }

  # Check timezone
  if ( !is.null(timezone) ) {
    if ( !timezone %in% OlsonNames() ) {
      stop("Invalid timezone")
    }
  }

  if ( !is.null(startdate) ) {
    startdate <- MazamaCoreUtils::parseDatetime(startdate, timezone = timezone)
    if ( startdate > range(mts_tidy$datetime)[2] ) {
      stop("startdate is outside of data date range")
    }
  }

  if ( !is.null(enddate) ) {
    enddate <- MazamaCoreUtils::parseDatetime(enddate, timezone = timezone)
    if ( enddate < range(mts_tidy$datetime)[1] ) {
      stop("enddate is outside of data date range")
    }
  }

  # ----- Prepare data ---------------------------------------------------------

  if ( !is.null(id) ) {
    mts_tidy <- dplyr::filter(mts_tidy, .data$deviceDeploymentID %in% id)
  }

  pm25LegendLabel <- "Hourly PM2.5 Values"
  nowcastLegendLabel <- "NowCast"

  if (length(unique(mts_tidy$deviceDeploymentID)) > 1) {
    mapping_pm25 <- mapping_nowcast <- aes_(color = ~deviceDeploymentID)
    if (is.null(title)) title <- ""

  } else {
    mapping_pm25 <- aes(color = !!pm25LegendLabel)
    mapping_nowcast <- aes(color = !!nowcastLegendLabel)
    if (is.null(title)) {
      title <- paste0("Hourly PM2.5 Values and NowCast\n",
                      "Site: ", unique(mts_tidy$locationName))
    }
  }

  # ----- Style ----------------------------------------------------------------

  if (style == "large") {
    pointsize <- 2
    linesize <- 0.8
    date_labels <- "%b %d"
    minor_break_width <- NULL
    base_size <- 15
  } else if (style == "small") {
    pointsize <- 2
    linesize <- 0.8
    date_labels <- "%b\n%d"
    minor_break_width <- "6 hours"
    base_size <- 11
  }

  # ----- Create plot ----------------------------------------------------------

  plot <-
    ggplot_pm25Timeseries(
      mts_tidy,
      startdate = startdate,
      enddate = enddate,
      includeFullEnddate = FALSE,
      date_labels = date_labels,
      minor_break_width = minor_break_width,
      base_size = base_size,
      ...
    ) +
    custom_aqiLines(size = 1, alpha = .8) +
    geom_pm25Points(mapping_pm25, size = pointsize) +
    stat_nowcast(mapping_nowcast, size = linesize) +
    custom_aqiStackedBar() +
    ggtitle(title)


  # ----- Handle legend --------------------------------------------------------

  # Add legend when plotting multiple monitors
  if (length(unique(mts_tidy$deviceDeploymentID)) == 1) {

    values <- c(1, 1)
    names(values) <- c(pm25LegendLabel, nowcastLegendLabel)
    scale <- scale_color_manual(name = "", values = values, labels = names(values))
    guide <- guides(
      color = guide_legend(
        title = "",
        override.aes = list(
          color = c(1, 1),
          size = c(pointsize, linesize),
          linetype = c(NA, 1),
          shape = c(16, NA),
          alpha = c(0.3, 1)
        )
      )
    )

    plot <- plot + scale + guide

  # Single monitor
  } else {
    plot <- plot + scale_color_brewer(palette = "Dark2")
  }

  plot <- plot + theme_timeseriesPlot_airfire(size = style)

  # ----- Return ---------------------------------------------------------------

  return(plot)

}
MazamaScience/AirMonitorPlots documentation built on Oct. 13, 2023, 8:49 a.m.