add 'spec_line' to inline plotting
diff --git a/R/mini_plots.R b/R/mini_plots.R
index f34715f..ebca6fd 100644
--- a/R/mini_plots.R
+++ b/R/mini_plots.R
@@ -2,7 +2,7 @@
 #'
 #' @description These functions helps you quickly generate sets of sparkline
 #' style plots using base R plotting system. Currently, we support histogram
-#' and boxplot. You can use them together with `column_spec` to
+#' boxplot, and line. You can use them together with `column_spec` to
 #' generate inline plot in tables. By default, this function will save images
 #' in a folder called "kableExtra" and return the address of the file.
 #'
@@ -12,12 +12,21 @@
 #' @param res The resolution of the plot. Default is 300.
 #' @param same_lim T/F. If x is a list of vectors, should all the plots be
 #' plotted in the same range? Default is True.
-#' @param lim Manually specify plotting range in the form of `c(0, 10)`.
+#' @param lim,xlim,ylim Manually specify plotting range in the form of
+#' `c(0, 10)`. `lim` is used in `spec_hist` and `spec_boxplot`; `xlim`
+#' and `ylim` are used in `spec_line`.
 #' @param xaxt On/Off for xaxis text
 #' @param yaxt On/Off for yaxis text
 #' @param ann On/Off for annotations (titles and axis titles)
 #' @param col Color for the fill of the histogram bar/boxplot box.
 #' @param border Color for the border.
+#' @param frame.plot On/Off for surrounding box (`spec_line` only). Default
+#' 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 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.
 #' @param dir Directory of where the images will be saved.
 #' @param file File name. If not provided, a random name will be used
 #' @param file_type Graphic device. Support `png` or `svg`. SVG is recommended
@@ -187,3 +196,117 @@
   return(fig_dir_name)
 }
 
+#' @rdname spec_hist
+#' @export
+spec_line <- function(x, y = NULL, width = 200, height = 50, res = 300,
+                      same_lim = TRUE, xlim = NULL, ylim = NULL,
+                      xaxt = 'n', yaxt = 'n', ann = FALSE,
+                      col = "lightgray", border = NULL,
+                      frame.plot = FALSE, lwd = 2,
+                      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", ...) {
+  if (is.list(x)) {
+    if (same_lim) {
+      if (is.null(xlim)) {
+        xlim <- base::range(unlist(x))
+      }
+      if (is.null(ylim) && !is.null(y)) {
+        ylim <- base::range(unlist(y))
+      }
+    }
+    if (is.null(y)) {
+      y <- replicate(length(x), NULL, simplify = FALSE)
+    } else if (!is.list(y) || length(x) != length(y)) {
+      stop("'x' and 'y' are not the same length")
+    }
+    return(Map(function(x_, y_) {
+      spec_line(x = x_, y = y_,
+                width = width, height = height,
+                same_lim = same_lim, xlim = xlim, ylim = ylim,
+                xaxt = xaxt, yaxt = yaxt, ann = ann, col = col, border = border,
+                frame.plot = frame.plot, lwd = lwd,
+                minmax = minmax, min = min, max = max,
+                dir = dir, file = file, file_type = file_type, ...)
+    }, x, y))
+  }
+
+  if (is.null(y) || !length(y)) {
+    y <- x
+    x <- seq(0, 1, length.out = length(y))
+    tmp <- ylim
+    ylim <- xlim
+    xlim <- tmp
+  }
+
+  if (is.null(xlim)) {
+    xlim <- base::range(x)
+  }
+
+  if (is.null(ylim) && !is.null(y)) {
+    ylim <- base::range(y)
+  }
+
+  if (is.null(min)) min <- minmax
+  if (is.null(max)) max <- minmax
+
+  expand <- c(
+    if (!is.null(min) && length(min)) 0.96 else 1,
+    if (!is.null(max) && length(max)) 1.04 else 1)
+  xlim <- xlim * expand
+  ylim <- ylim * expand
+
+  file_type <- match.arg(file_type, c("svg", "png"))
+
+  if (!dir.exists(dir)) {
+    dir.create(dir)
+  }
+
+  if (is.null(file)) {
+    file <- file.path(dir, paste0(
+      "hist_", round(as.numeric(Sys.time()) * 1000), ".", file_type))
+  }
+
+  if (file_type == "svg") {
+    grDevices::svg(filename = file, width = width / res, height = height / res,
+                   bg = 'transparent')
+  } else {
+    grDevices::png(filename = file, width = width, height = height, res = res,
+                   bg = 'transparent')
+  }
+  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, border = border,
+                 xaxt = xaxt, yaxt = yaxt, ann = ann, col = col,
+                 frame.plot = frame.plot, ...)
+
+  if (!is.null(min) && length(min)) {
+    ind <- which.min(y)
+    do.call(graphics::points, c(list(x[ind], y[ind]), min))
+  }
+
+  if (!is.null(max) && length(max)) {
+    ind <- which.max(y)
+    do.call(graphics::points, c(list(x[ind], y[ind]), max))
+  }
+
+  grDevices::dev.off(curdev)
+
+  if (file_type == "svg") {
+    svg_xml <- xml2::read_xml(file)
+    svg_text <- as.character(svg_xml)
+    unlink(file)
+  } else {
+    svg_text <- NULL
+  }
+  out <- list(path = file, dev = file_type, type = "line",
+              width = width, height = height, res = res,
+              svg_text = svg_text)
+
+  class(out) <- "kableExtraInlinePlots"
+  return(out)
+}