Merge pull request #502 from r2evans/add/spec_line

add 'spec_line' to inline plotting
diff --git a/NAMESPACE b/NAMESPACE
index eda523b..b8ebdd2 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -46,6 +46,7 @@
 export(spec_font_size)
 export(spec_hist)
 export(spec_image)
+export(spec_line)
 export(spec_popover)
 export(spec_tooltip)
 export(text_spec)
diff --git a/R/mini_plots.R b/R/mini_plots.R
index f34715f..8f13706 100644
--- a/R/mini_plots.R
+++ b/R/mini_plots.R
@@ -1,8 +1,8 @@
 #' Helper functions to generate inline sparklines
 #'
 #' @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
+#' style plots using base R plotting system. Currently, we support histogram,
+#' 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)
+}
diff --git a/man/spec_hist.Rd b/man/spec_hist.Rd
index 9deb046..cfdeed3 100644
--- a/man/spec_hist.Rd
+++ b/man/spec_hist.Rd
@@ -3,6 +3,7 @@
 \name{spec_hist}
 \alias{spec_hist}
 \alias{spec_boxplot}
+\alias{spec_line}
 \title{Helper functions to generate inline sparklines}
 \usage{
 spec_hist(
@@ -46,6 +47,31 @@
   file_type = if (is_latex()) "png" else "svg",
   ...
 )
+
+spec_line(
+  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",
+  ...
+)
 }
 \arguments{
 \item{x}{Vector of values or List of vectors of values.}
@@ -76,7 +102,9 @@
 \item{same_lim}{T/F. If x is a list of vectors, should all the plots be
 plotted in the same range? Default is True.}
 
-\item{lim}{Manually specify plotting range in the form of \code{c(0, 10)}.}
+\item{lim, xlim, ylim}{Manually specify plotting range in the form of
+\code{c(0, 10)}. \code{lim} is used in \code{spec_hist} and \code{spec_boxplot}; \code{xlim}
+and \code{ylim} are used in \code{spec_line}.}
 
 \item{xaxt}{On/Off for xaxis text}
 
@@ -109,11 +137,21 @@
 \item{medcol}{Boxplot - median line color}
 
 \item{medlwd}{Boxplot - median line width}
+
+\item{frame.plot}{On/Off for surrounding box (\code{spec_line} only). Default
+is False.}
+
+\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{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.}
 }
 \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 \code{column_spec} to
+style plots using base R plotting system. Currently, we support histogram,
+boxplot, and line. You can use them together with \code{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.
 }
diff --git a/vignettes/awesome_table_in_html.Rmd b/vignettes/awesome_table_in_html.Rmd
index ae75688..cb7b1e3 100644
--- a/vignettes/awesome_table_in_html.Rmd
+++ b/vignettes/awesome_table_in_html.Rmd
@@ -235,16 +235,17 @@
     c("kableExtra_sm.png", "kableExtra_sm.png"), 50, 50))
 ```
 
-`kableExtra` also provides a few inline plotting tools. Right now, there are `spec_hist` and `spec_boxplot`. One key feature is that by default, the limits of every subplots are fixed so you can compare across rows. Note that in html, you can also use package `sparkline` to create some jquery based interactive sparklines. Check out the end of this guide for details. 
+`kableExtra` also provides a few inline plotting tools. Right now, there are `spec_hist`, `spec_boxplot`, and `spec_line`. One key feature is that by default, the limits of every subplots are fixed so you can compare across rows. Note that in html, you can also use package `sparkline` to create some jquery based interactive sparklines. Check out the end of this guide for details.
 
 ```{r}
 mpg_list <- split(mtcars$mpg, mtcars$cyl)
-inline_plot <- data.frame(cyl = c(4, 6, 8), mpg_box = "", mpg_hist = "")
+inline_plot <- data.frame(cyl = c(4, 6, 8), mpg_box = "", mpg_hist = "", mpg_line = "")
 inline_plot %>% 
   kbl(booktabs = T) %>%
   kable_paper(full_width = F) %>%
   column_spec(2, image = spec_boxplot(mpg_list)) %>%
-  column_spec(3, image = spec_hist(mpg_list))
+  column_spec(3, image = spec_hist(mpg_list)) %>%
+  column_spec(4, image = spec_line(mpg_list, same_lim = FALSE))
 ```
 
 ## Row spec
diff --git a/vignettes/awesome_table_in_pdf.Rmd b/vignettes/awesome_table_in_pdf.Rmd
index 4046d9e..2fd3b25 100644
--- a/vignettes/awesome_table_in_pdf.Rmd
+++ b/vignettes/awesome_table_in_pdf.Rmd
@@ -263,16 +263,17 @@
     c("kableExtra_sm.png", "kableExtra_sm.png"), 50, 50))
 ```
 
-`kableExtra` also provides a few inline plotting tools. Right now, there are `spec_hist` and `spec_boxplot`. One key feature is that by default, the limits of every subplots are fixed so you can compare across rows. 
+`kableExtra` also provides a few inline plotting tools. Right now, there are `spec_hist`, `spec_boxplot`, and `spec_line`. One key feature is that by default, the limits of every subplots are fixed so you can compare across rows.
 
 ```{r}
 mpg_list <- split(mtcars$mpg, mtcars$cyl)
-inline_plot <- data.frame(cyl = c(4, 6, 8), mpg_box = "", mpg_hist = "")
+inline_plot <- data.frame(cyl = c(4, 6, 8), mpg_box = "", mpg_hist = "", mpg_line = "")
 inline_plot %>% 
   kbl(booktabs = T) %>%
   kable_paper(full_width = F) %>%
   column_spec(2, image = spec_boxplot(mpg_list)) %>%
-  column_spec(3, image = spec_hist(mpg_list))
+  column_spec(3, image = spec_hist(mpg_list)) %>%
+  column_spec(4, image = spec_line(mpg_list, same_lim = FALSE))
 ```
 
 ## Row spec