llplot <- function(measure, yi, vi, sei, ai, bi, ci, di, n1i, n2i, data, subset, drop00=TRUE,
xvals=1000, xlim, ylim, xlab, ylab, scale=TRUE,
lty, lwd, col, level=99.99, refline=0, ...) {

   #########################################################################

   mstyle <- .get.mstyle("crayon" %in% .packages())

   ### data setup

   if (missing(measure))
      stop(mstyle$stop("Must specify an effect size or outcome measure via the 'measure' argument."))

   if (!is.element(measure, c("GEN", "OR")))
      stop(mstyle$stop("Currently only measure=\"GEN\" or measure=\"OR\" can be specified."))

   na.act <- getOption("na.action")

   if (!is.element(na.act, c("na.omit", "na.exclude", "na.fail", "na.pass")))
      stop(mstyle$stop("Unknown 'na.action' specified under options()."))

   if (measure == "OR" && !requireNamespace("BiasedUrn", quietly=TRUE))
      stop(mstyle$stop("Please install the 'BiasedUrn' package to use this function."))

   if (missing(xlab)) {
      if (measure == "GEN")
         xlab <- "Observed Outcome"
      if (measure == "OR")
         xlab <- "Log Odds Ratio"
   }

   if (missing(ylab)) {
      if (scale) {
         ylab <- "Scaled Likelihood"
      } else {
         ylab <- "Likelihood"
      }
   }

   level <- ifelse(level == 0, 1, ifelse(level >= 1, (100-level)/100, ifelse(level > .5, 1-level, level)))

   ### get ... argument

   ddd <- list(...)

   ### set defaults or get onlyo1, addyi, and addvi arguments

   onlyo1 <- ifelse(is.null(ddd$onlyo1), FALSE, ddd$onlyo1)
   addyi  <- ifelse(is.null(ddd$addyi),  TRUE,  ddd$addyi)
   addvi  <- ifelse(is.null(ddd$addvi),  TRUE,  ddd$addvi)

   #########################################################################

   ### check if data argument has been specified

   if (missing(data))
      data <- NULL

   if (is.null(data)) {
      data <- sys.frame(sys.parent())
   } else {
      if (!is.data.frame(data))
         data <- data.frame(data)
   }

   ### extract values, possibly from the data frame specified via data (arguments not specified are NULL)

   mf <- match.call()
   mf.subset <- mf[[match("subset", names(mf))]]
   mf.lty    <- mf[[match("lty",    names(mf))]]
   mf.lwd    <- mf[[match("lwd",    names(mf))]]
   mf.col    <- mf[[match("col",    names(mf))]]
   subset <- eval(mf.subset, data, enclos=sys.frame(sys.parent()))
   lty    <- eval(mf.lty,    data, enclos=sys.frame(sys.parent()))
   lwd    <- eval(mf.lwd,    data, enclos=sys.frame(sys.parent()))
   col    <- eval(mf.col,    data, enclos=sys.frame(sys.parent()))

   if (measure == "GEN") {

      mf.yi  <- mf[[match("yi",  names(mf))]]
      mf.vi  <- mf[[match("vi",  names(mf))]]
      mf.sei <- mf[[match("sei", names(mf))]]
      yi  <- eval(mf.yi,  data, enclos=sys.frame(sys.parent()))
      vi  <- eval(mf.vi,  data, enclos=sys.frame(sys.parent()))
      sei <- eval(mf.sei, data, enclos=sys.frame(sys.parent()))

      if (is.null(vi)) {
         if (is.null(sei)) {
            stop(mstyle$stop("Need to specify 'vi' or 'sei' argument."))
         } else {
            vi <- sei^2
         }
      }

      if (length(yi)==0L || length(vi)==0L)
         stop(mstyle$stop("Cannot extract outcomes. Check that all of the required \n  information is specified via the appropriate arguments."))

      if (length(yi) != length(vi))
         stop(mstyle$stop("Supplied data vectors are not all of the same length."))

      k <- length(yi) ### number of outcomes before subsetting

      ### subsetting

      if (!is.null(subset)) {
         yi <- yi[subset]
         vi <- vi[subset]
      }

   }

   if (measure == "OR") {

      mf.ai  <- mf[[match("ai",  names(mf))]]
      mf.bi  <- mf[[match("bi",  names(mf))]]
      mf.ci  <- mf[[match("ci",  names(mf))]]
      mf.di  <- mf[[match("di",  names(mf))]]
      mf.n1i <- mf[[match("n1i", names(mf))]]
      mf.n2i <- mf[[match("n2i", names(mf))]]
      ai  <- eval(mf.ai,  data, enclos=sys.frame(sys.parent()))
      bi  <- eval(mf.bi,  data, enclos=sys.frame(sys.parent()))
      ci  <- eval(mf.ci,  data, enclos=sys.frame(sys.parent()))
      di  <- eval(mf.di,  data, enclos=sys.frame(sys.parent()))
      n1i <- eval(mf.n1i, data, enclos=sys.frame(sys.parent()))
      n2i <- eval(mf.n2i, data, enclos=sys.frame(sys.parent()))
      if (is.null(bi)) bi <- n1i - ai
      if (is.null(di)) di <- n2i - ci

      if (length(ai)==0L || length(bi)==0L || length(ci)==0L || length(di)==0L)
         stop(mstyle$stop("Cannot compute outcomes. Check that all of the required \n  information is specified via the appropriate arguments."))

      if (!all(length(ai) == c(length(ai),length(bi),length(ci),length(di))))
         stop(mstyle$stop("Supplied data vectors are not all of the same length."))

      if (any(c(ai > n1i, ci > n2i), na.rm=TRUE))
         stop(mstyle$stop("One or more event counts are larger than the corresponding group sizes."))

      if (any(c(ai, bi, ci, di) < 0, na.rm=TRUE))
         stop(mstyle$stop("One or more counts are negative."))

      k <- length(ai) ### number of outcomes before subsetting

      ### note studies that have at least one zero cell

      id0 <- c(ai == 0L | bi == 0L | ci == 0L | di == 0L)
      id0[is.na(id0)] <- FALSE

      ### note studies that have no events or all events

      id00 <- c(ai == 0L & ci == 0L) | c(bi == 0L & di == 0L)
      id00[is.na(id00)] <- FALSE

      ### if drop00=TRUE, set counts to NA for studies that have no events (or all events) in both arms

      if (drop00) {
         ai[id00] <- NA
         bi[id00] <- NA
         ci[id00] <- NA
         di[id00] <- NA
      }

      ### subsetting

      if (!is.null(subset)) {
         ai <- ai[subset]
         bi <- bi[subset]
         ci <- ci[subset]
         di <- di[subset]
      }

      dat <- escalc(measure="OR", ai=ai, bi=bi, ci=ci, di=di, drop00=drop00, onlyo1=onlyo1, addyi=addyi, addvi=addvi)

      yi <- dat$yi ### one or more yi/vi pairs may be NA/NA
      vi <- dat$vi ### one or more yi/vi pairs may be NA/NA

   }

   #########################################################################

   ### study ids (1:k sequence before subsetting)

   ids <- seq_len(k)

   ### setting of lty, lwd, and col arguments (if a single value, repeat k times)
   ### if any of these arguments is not a single value, it must have the same length as the data before subsetting

   if (!is.null(lty)) {
      if (length(lty) == 1L) {
         lty <- rep(lty, k)
      } else {
         if (length(lty) != k)
            stop(mstyle$stop("Length of 'lty' argument does not match length of data."))
      }
   }

   if (!is.null(lwd)) {
      if (length(lwd) == 1L) {
         lwd <- rep(lwd, k)
      } else {
         if (length(lwd) != k)
            stop(mstyle$stop("Length of 'lwd' argument does not match length of data."))
      }
   }

   if (!is.null(col)) {
      if (length(col) == 1L) {
         col <- rep(col, k)
      } else {
         if (length(col) != k)
            stop(mstyle$stop("Length of 'col' argument does not match length of data."))
      }
   }

   ### if a subset of studies is specified

   if (!is.null(subset)) {
      ids  <- ids[subset]
      lty  <- lty[subset]
      lwd  <- lwd[subset]
      col  <- col[subset]
      id0  <- id0[subset]
      id00 <- id00[subset]
   }

   ### number of outcomes after subsetting

   k <- length(yi)

   ### check for NAs and act accordingly

   if (measure == "GEN") {
      has.na <- is.na(yi) | is.na(vi)
   }
   if (measure == "OR") {
      has.na <- is.na(ai) | is.na(bi) | is.na(ci) | is.na(di)
   }

   not.na <- !has.na

   if (any(has.na)) {

      if (na.act == "na.omit" || na.act == "na.exclude" || na.act == "na.pass") {
         yi   <- yi[not.na]
         vi   <- vi[not.na]
         ai   <- ai[not.na]
         bi   <- bi[not.na]
         ci   <- ci[not.na]
         di   <- di[not.na]
         ids  <- ids[not.na]
         lty  <- lty[not.na]
         lwd  <- lwd[not.na]
         col  <- col[not.na]
         id0  <- id0[not.na]
         id00 <- id00[not.na]
         k    <- length(ai)
         warning(mstyle$warning("Studies with NAs omitted from plotting."))
      }

      if (na.act == "na.fail")
         stop(mstyle$stop("Missing values in studies."))

   }

   ### at least one study left?

   if (k < 1)
      stop(mstyle$stop("Processing terminated since k = 0."))

   #########################################################################

   ### set default line types (id0 studies = dashed line, id00 studies = dotted line, all others = solid line)

   if (measure == "GEN") {
      if (is.null(lty))
         lty <- rep("solid", k)
   }

   if (measure == "OR") {
      if (is.null(lty))
         lty <- ifelse(id0 | id00, ifelse(id00, "dotted", "dashed"), "solid")
   }

   ### set default line widths (4.0 to 0.2 according to the rank of vi)

   if (is.null(lwd))
      lwd <- seq(from=4.0, to=0.2, length.out=k)[rank(vi)]

   ### set default line color (gray0 to gray80 according to the rank of vi)

   if (is.null(col))
      col <- paste0("gray", round(seq(from=0, to=80, length.out=k))[rank(vi)])

   ### set x axis limits

   ci.lb <- yi - qnorm(level/2, lower.tail=FALSE) * sqrt(vi)
   ci.ub <- yi + qnorm(level/2, lower.tail=FALSE) * sqrt(vi)

   if (missing(xlim)) {
      xlim <- c(min(ci.lb, na.rm=TRUE),max(ci.ub, na.rm=TRUE))
   } else {
      xlim <- sort(xlim)
   }

   xs <- seq(from=xlim[1], to=xlim[2], length.out=xvals)

   lls <- matrix(NA_real_, nrow=k, ncol=xvals)
   out <- matrix(TRUE, nrow=k, ncol=xvals)

   if (measure == "GEN") {

      for (i in seq_len(k)) {
         for (j in seq_len(xvals)) {
            lls[i,j] <- dnorm(yi[i], xs[j], sqrt(vi[i]))
            if (xs[j] >= ci.lb[i] & xs[j] <= ci.ub[i])
               out[i,j] <- FALSE
         }
      }

   }

   if (measure == "OR") {

      for (i in seq_len(k)) {
         for (j in seq_len(xvals)) {
            lls[i,j] <- .dnchgi(xs[j], ai=ai[i], bi=bi[i], ci=ci[i], di=di[i], random=FALSE, dnchgcalc="dFNCHypergeo", dnchgprec=1e-10)
            if (xs[j] >= ci.lb[i] & xs[j] <= ci.ub[i])
               out[i,j] <- FALSE
         }
      }

   }

   if (scale) {
      trapezoid <- function(x,y) sum(diff(x)*(y[-1]+y[-length(y)]))/2
      lls.sum <- rep(NA_real_, k)
      for (i in seq_len(k)) {
         lls.sum[i] <- trapezoid(xs[!is.na(lls[i,])], lls[i,!is.na(lls[i,])])
      }
      #lls.sum <- rowSums(lls, na.rm=TRUE)
      lls <- apply(lls, 2, "/", lls.sum)
   }

   lls[out] <- NA

   ### set y axis limits

   if (missing(ylim)) {
      ylim <- c(0, max(lls, na.rm=TRUE))
   } else {
      ylim <- sort(ylim)
   }

   plot(NA, NA, xlim=c(xlim[1], xlim[2]), ylim=ylim, xlab=xlab, ylab=ylab, ...)

   for (i in seq_len(k)[order(1/vi)]) {
      lines(xs, lls[i,], lty=lty[i], lwd=lwd[i], col=col[i], ...)
   }

   if (is.numeric(refline))
      abline(v=refline, lty="solid", lwd=2, ...)

   invisible(lls)

}
