Merge branch 'fix/522_specline' of github.com:r2evans/kableExtra into fix/522_specline
diff --git a/R/cell_spec.R b/R/cell_spec.R
index 8a69c1f..5e58b1c 100644
--- a/R/cell_spec.R
+++ b/R/cell_spec.R
@@ -117,10 +117,10 @@
 
   # favor popover over tooltip
   if (!is.null(popover)) {
-    if (class(popover) != "ke_popover") popover <- spec_popover(popover)
+    if (!inherits(popover, "ke_popover")) popover <- spec_popover(popover)
     tooltip_n_popover <- popover
   } else if (!is.null(tooltip)) {
-    if (class(tooltip) != "ke_tooltip") tooltip <- spec_tooltip(tooltip)
+    if (!inherits(tooltip, "ke_tooltip")) tooltip <- spec_tooltip(tooltip)
     tooltip_n_popover <- tooltip
   } else {
     tooltip_n_popover <- NULL
diff --git a/R/column_spec.R b/R/column_spec.R
index 77f0c69..14c0ea1 100644
--- a/R/column_spec.R
+++ b/R/column_spec.R
@@ -264,9 +264,9 @@
                                              extra_css)
   }
 
-  if (!is.null(image)) {
+  if (!is.null(image) && (length(image) > 1 || !is.null(image[[1]]))) {
     image <- image[[1]]
-    if (class(image) == "kableExtraInlinePlots") {
+    if (inherits(image, "kableExtraInlinePlots")) {
       if (!is.null(image$svg_text)) {
         xml_add_child(target_cell, xml2::read_xml(image$svg_text))
       } else {
@@ -284,13 +284,13 @@
 
   # favor popover over tooltip
   if (!is.null(popover)) {
-    if (class(popover) != "ke_popover") popover <- spec_popover(popover)
+    if (!inherits(popover, "ke_popover")) popover <- spec_popover(popover)
     popover_list <- attr(popover, 'list')
     for (p in names(popover_list)) {
       xml_attr(target_cell, p) <- popover_list[p]
     }
   } else if (!is.null(tooltip)) {
-    if (class(tooltip) != "ke_tooltip") tooltip <- spec_tooltip(tooltip)
+    if (!inherits(tooltip, "ke_tooltip")) tooltip <- spec_tooltip(tooltip)
     tooltip_list <- attr(tooltip, 'list')
     for (t in names(tooltip_list)) {
       xml_attr(target_cell, t) <- tooltip_list[t]
@@ -539,9 +539,9 @@
                               new_row[column], "\\}")
   }
 
-  if (!is.null(image)) {
+  if (!is.null(image) && (length(image) > 1 || !is.null(image[[1]]))) {
     image <- image[[1]]
-    if (class(image) == "kableExtraInlinePlots") {
+    if (inherits(image, "kableExtraInlinePlots")) {
       new_row[column] <- paste0(
         new_row[column],
         '\\\\includegraphics\\[width=',
diff --git a/R/kable_as_image.R b/R/kable_as_image.R
index ffe18be..6a0098d 100644
--- a/R/kable_as_image.R
+++ b/R/kable_as_image.R
@@ -67,7 +67,7 @@
     table_img_pdf <- try(magick::image_read(paste0(temp_file, ".pdf"),
                                             density = density),
                          silent = T)
-    if (class(table_img_pdf) == "try-error") {
+    if (inherits(table_img_pdf, "try-error")) {
       stop("Ghostscript is required to read PDF on windows. ",
            "Please download it here: https://ghostscript.com/")
     }
diff --git a/R/mini_plots.R b/R/mini_plots.R
index 125990a..068cc82 100644
--- a/R/mini_plots.R
+++ b/R/mini_plots.R
@@ -46,6 +46,8 @@
     )}))
   }
 
+  if (is.null(x)) return(NULL)
+
   if (is.null(lim)) {
     lim <- base::range(x)
   }
@@ -147,6 +149,8 @@
     )}))
   }
 
+  if (is.null(x)) return(NULL)
+
   if (is.null(lim)) {
     lim <- base::range(x)
     lim[1] <- lim[1] - (lim[2] - lim[1]) / 10
@@ -239,6 +243,16 @@
 #' is False.
 #' @param lwd Line width for `spec_line`; within `spec_line`, the `minmax`
 #' argument defaults to use this value for `cex` for points. Default is 2.
+#' @param pch,cex Shape and size for points (if type is other than "l").
+#' @param type Passed to `plot`, often one of "l", "p", or "b", see
+#' [graphics::plot.default()] for more details. Ignored when 'polymin' is
+#' not 'NA'.
+#' @param polymin Special argument that converts a "line" to a polygon,
+#' where the flat portion is this value, and the other side of the polygon
+#' is the 'y' value ('x' if no 'y' provided). If 'NA' (the default), then
+#' this is ignored, otherwise if this is numeric then a polygon is
+#' created instead (and 'type' is ignored). Note that if 'polymin' is in
+#' the middle of the 'y' values, it will generate up/down polygons.
 #' @param minmax,min,max Arguments passed to `points` to highlight minimum
 #' and maximum values in `spec_line`. If `min` or `max` are `NULL`, they
 #' default to the value of `minmax`. Set to an empty `list()` to disable.
@@ -254,20 +268,24 @@
                       xaxt = 'n', yaxt = 'n', ann = FALSE,
                       col = "lightgray", border = NULL,
                       frame.plot = FALSE, lwd = 2,
+                      pch = ".", cex = 0.1, type = "l", polymin = NA,
                       minmax = list(pch = ".", cex = lwd, col = "red"),
                       min = minmax, max = minmax,
                       dir = if (is_latex()) rmd_files_dir() else tempdir(),
-                      file = NULL,
-                      file_type = if (is_latex()) "png" else "svg", ...) {
+                      file = NULL, file_type = if (is_latex()) "png" else "svg", ...) {
   if (is.list(x)) {
     lenx <- length(x)
 
     if (same_lim) {
       if (is.null(xlim)) {
-        xlim <- lapply(x, base::range)
+        xlim <- lapply(x, function(z) base::range(c(z, if (is.null(y)) polymin), na.rm = TRUE))
       }
       if (is.null(ylim) && !is.null(y)) {
-        ylim <- lapply(y, base::range)
+        if (is.list(y)) {
+          ylim <- lapply(y, function(z) base::range(c(z, polymin), na.rm = TRUE))
+        } else {
+          ylim <- base::range(c(y, polymin), na.rm = TRUE)
+        }
       }
     }
     if (is.null(y)) {
@@ -282,27 +300,37 @@
     # enforce a restriction of recycling only length 1 or lenx
 
     # first, start with the literals (x,y)
-    # same_lim, not a factor anymore
+    # (same_lim is not a factor anymore)
     dots <- list(x = x, y = y)
 
     # second, we know these args are likely to be vectors (length > 1)
     # or lists, so we have to handle them carefully and double-list if
     # present
-    notlen1 <- list(xlim = xlim, ylim = ylim,
-                    minmax = minmax, min = min, max = max)
+    notlen1vec <- list(xlim = xlim, ylim = ylim)
     dots <- c(dots, Map(
       function(L, nm) {
         if (is.null(L)) return(list(NULL))
-        if (!is.list(L) || !is.list(L[[1]])) return(list(L))
-        if (!length(L) %in% c(1L, lenx)) {
-          stop("length of '", nm, "' must be 1 or the same length as 'x'")
-        }
-        L
-      }, notlen1, names(notlen1)))
+        if (!is.list(L)) return(list(L))
+        if (length(L) == lenx) return(L)
+        stop("length of '", nm, "' must be 1 or the same length as 'x'")
+      }, notlen1vec, names(notlen1vec)))
+
+    # these are special ... they are lists which may need to be
+    # nested, and we can't pass NULL since that may default to the
+    # actual values instead of the intended
+    notlen1lst <- list(minmax = minmax, min = min, max = max)
+    dots <- c(dots, Map(
+      function(L, nm) {
+        if (is.null(L)) return(list(NULL))
+        if (!length(L)) return(list(list()))
+        if (!is.list(L[[1]])) return (list(L))
+        if (length(L) == lenx) return(L)
+        stop("length of '", nm, "' must be 1 or the same length as 'x'")
+      }, notlen1lst, names(notlen1lst)))
 
     # last, all remaining arguments that we don't already know about
     # are length-1, so can be easily listified; using 'formals()'
-    # allows us to not hard-code all params, for future expansion?
+    # allows us to not hard-code all params
     len1args <- mget(setdiff(names(formals()),
                              c(names(dots), "same_lim", "x", "y", "...")))
     dots <- c(dots, Map(
@@ -318,12 +346,15 @@
 
   }
 
+  if (is.null(x)) return(NULL)
+
   if (is.null(y) || !length(y)) {
     y <- x
-    x <- seq(0, 1, length.out = length(y))
-    tmp <- ylim
-    ylim <- xlim
-    xlim <- tmp
+    x <- seq_along(y)
+    if (!is.null(xlim) && is.null(ylim)) {
+      ylim <- xlim
+      xlim <- range(x)
+    }
   }
 
   if (is.null(xlim)) {
@@ -331,7 +362,7 @@
   }
 
   if (is.null(ylim) && !is.null(y)) {
-    ylim <- base::range(y)
+    ylim <- base::range(c(y, polymin), na.rm = TRUE)
   }
 
   if (is.null(min)) min <- minmax
@@ -364,10 +395,29 @@
   curdev <- grDevices::dev.cur()
   on.exit(grDevices::dev.off(curdev), add = TRUE)
 
-  graphics::par(mar = c(0, 0, 0.2, 0), lwd = lwd)
-  graphics::plot(x, y, type = "l", xlim = xlim, ylim = ylim,
+  graphics::par(mar = c(0, 0, 0, 0), lwd = lwd)
+
+  dots <- list(...)
+  if (!is.na(polymin) && "angle" %in% names(dots)) {
+    angle <- dots$angle
+    dots$angle <- NULL
+  } else angle <- 45
+
+  do.call(graphics::plot,
+          c(list(x, y, type = if (is.na(polymin)) type else "n",
+                 xlim = xlim, ylim = ylim,
                  xaxt = xaxt, yaxt = yaxt, ann = ann, col = col,
-                 frame.plot = frame.plot, ...)
+                 frame.plot = frame.plot, xpd = NA,
+                 cex = cex, pch = pch # in case of type="p" or similar
+                 ), dots))
+
+  if (!is.na(polymin)) {
+    lty <- if ("lty" %in% names(dots)) dots$lty else graphics::par("lty")
+    polygon(c(x[1], x, x[length(x)]), c(polymin, y, polymin),
+            border = NA, col = col, angle = angle,
+            lty = lty,
+            xpd = NA)
+  }
 
   if (!is.null(min) && length(min)) {
     ind <- which.min(y)
diff --git a/R/save_kable.R b/R/save_kable.R
index 974ab70..e1e1636 100644
--- a/R/save_kable.R
+++ b/R/save_kable.R
@@ -167,7 +167,7 @@
     table_img_pdf <- try(
       magick::image_read(paste0(file_no_ext, ".pdf"),
                          density = density), silent = T)
-    if (class(table_img_pdf) == "try-error") {
+    if (inherits(table_img_pdf, "try-error")) {
       stop("We hit an error when trying to use magick to read the generated ",
            "PDF file. You may check your magick installation and try to ",
            "use magick::image_read to read the PDF file manually. It's also ",
diff --git a/R/xtable2kable.R b/R/xtable2kable.R
index 3c13658..478d6f2 100644
--- a/R/xtable2kable.R
+++ b/R/xtable2kable.R
@@ -25,7 +25,7 @@
 #'
 #' @export
 xtable2kable <- function(x, ...) {
-  if (!class(x)[1] == "xtable") {
+  if (!inherits(x, "xtable")) {
     warning("x is not an xtable object.")
     return(x)
   }
diff --git a/man/spec_line.Rd b/man/spec_line.Rd
index 8d881d1..fca9b4d 100644
--- a/man/spec_line.Rd
+++ b/man/spec_line.Rd
@@ -20,6 +20,10 @@
   border = NULL,
   frame.plot = FALSE,
   lwd = 2,
+  pch = ".",
+  cex = 0.1,
+  type = "l",
+  polymin = NA,
   minmax = list(pch = ".", cex = lwd, col = "red"),
   min = minmax,
   max = minmax,
@@ -60,6 +64,19 @@
 \item{lwd}{Line width for \code{spec_line}; within \code{spec_line}, the \code{minmax}
 argument defaults to use this value for \code{cex} for points. Default is 2.}
 
+\item{pch, cex}{Shape and size for points (if type is other than "l").}
+
+\item{type}{Passed to \code{plot}, often one of "l", "p", or "b", see
+\code{\link[graphics:plot.default]{graphics::plot.default()}} for more details. Ignored when 'polymin' is
+not 'NA'.}
+
+\item{polymin}{Special argument that converts a "line" to a polygon,
+where the flat portion is this value, and the other side of the polygon
+is the 'y' value ('x' if no 'y' provided). If 'NA' (the default), then
+this is ignored, otherwise if this is numeric then a polygon is
+created instead (and 'type' is ignored). Note that if 'polymin' is in
+the middle of the 'y' values, it will generate up/down polygons.}
+
 \item{minmax, min, max}{Arguments passed to \code{points} to highlight minimum
 and maximum values in \code{spec_line}. If \code{min} or \code{max} are \code{NULL}, they
 default to the value of \code{minmax}. Set to an empty \code{list()} to disable.}