Merge pull request #523 from r2evans/fix/522_specline
fix #522, spec_line glitches and improvements
diff --git a/DESCRIPTION b/DESCRIPTION
index a338828..55d9a67 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -16,7 +16,8 @@
person('Brian', 'Salzer', role = 'ctb'),
person('George', 'Gui', role = 'ctb'),
person('Yeliang', 'Fan', role = 'ctb'),
- person('Duncan', 'Murdoch', role = 'ctb')
+ person('Duncan', 'Murdoch', role = 'ctb'),
+ person('Bill', 'Evans', role = 'ctb')
)
Description: Build complex HTML or 'LaTeX' tables using 'kable()' from 'knitr'
and the piping syntax from 'magrittr'. Function 'kable()' is a light weight
diff --git a/NAMESPACE b/NAMESPACE
index b8ebdd2..005da73 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -46,7 +46,7 @@
export(spec_font_size)
export(spec_hist)
export(spec_image)
-export(spec_line)
+export(spec_plot)
export(spec_popover)
export(spec_tooltip)
export(text_spec)
diff --git a/R/mini_plots.R b/R/mini_plots.R
index 1c15262..a1463f9 100644
--- a/R/mini_plots.R
+++ b/R/mini_plots.R
@@ -110,7 +110,7 @@
#' plotted in the same range? Default is True.
#' @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`.
+#' and `ylim` are used in `spec_plot`.
#' @param xaxt On/Off for xaxis text
#' @param yaxt On/Off for yaxis text
#' @param ann On/Off for annotations (titles and axis titles)
@@ -239,12 +239,22 @@
#' @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
+#' @param frame.plot On/Off for surrounding box (`spec_plot` only). Default
#' is False.
-#' @param lwd Line width for `spec_line`; within `spec_line`, the `minmax`
+#' @param lwd Line width for `spec_plot`; within `spec_plot`, 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 (and 'type' is ignored). Note that if 'polymin' is in the middle
+#' of the 'y' values, it will generate up/down polygons around this value.
#' @param minmax,min,max Arguments passed to `points` to highlight minimum
-#' and maximum values in `spec_line`. If `min` or `max` are `NULL`, they
+#' and maximum values in `spec_plot`. 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
@@ -253,67 +263,113 @@
#' @param ... extra parameters passing to `plot`
#'
#' @export
-spec_line <- function(x, y = NULL, width = 200, height = 50, res = 300,
+spec_plot <- 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"),
+ pch = ".", cex = 2, type = "l", polymin = NA,
+ minmax = list(pch = ".", cex = cex, 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 <- base::range(unlist(x))
+ xlim <- base::range(unlist(x), na.rm = TRUE)
}
if (is.null(ylim) && !is.null(y)) {
- ylim <- base::range(unlist(y))
+ ylim <- base::range(c(unlist(y), polymin), na.rm = TRUE)
}
}
+
if (is.null(y)) {
- y <- replicate(length(x), NULL, simplify = FALSE)
- } else if (!is.list(y) || length(x) != length(y)) {
+ y <- list(y)
+ } else if (length(y) != lenx) {
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))
+
+ # any of the arguments can be per-plot controlling, but an arg
+ # that is normally not length-1 may be recycled (incorrectly) by
+ # Map, so we have to listify them if not already lists;
+ # enforce a restriction of recycling only length 1 or lenx
+
+ # first, start with the literals (x,y)
+ # (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
+ notlen1vec <- list(xlim = xlim, ylim = ylim)
+ dots <- c(dots, Map(
+ function(L, nm) {
+ if (is.null(L)) return(list(NULL))
+ 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
+ len1args <- mget(setdiff(names(formals()),
+ c(names(dots), "same_lim", "x", "y", "...")))
+ dots <- c(dots, Map(
+ function(V, nm) {
+ if (is.null(V)) return(list(NULL))
+ if (!length(V) %in% c(1L, lenx)) {
+ stop("length of '", nm, "' must be 1 or the same length as 'x'")
+ }
+ V
+ }, len1args, names(len1args)))
+
+ return(do.call(Map, c(list(f = spec_plot), dots)))
+
}
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 <- range(c(xlim, polymin), na.rm = TRUE)
+ xlim <- range(x)
+ }
}
if (is.null(xlim)) {
- xlim <- base::range(x)
+ xlim <- base::range(x, na.rm = TRUE)
}
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
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
+ if (!is.null(min) && length(min)) -0.04 else 0,
+ if (!is.null(max) && length(max)) +0.04 else 0)
+ xlim <- xlim + diff(xlim) * expand
+ ylim <- ylim + diff(ylim) * expand
file_type <- match.arg(file_type, c("svg", "png"))
@@ -336,17 +392,36 @@
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,
+ 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, cex = cex, pch = pch),
+ 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 = if ("xpd" %in% names(dots)) dots$xpd else NA)
+ }
if (!is.null(min) && length(min)) {
+ if (!"xpd" %in% names(min)) min$xpd <- NA
ind <- which.min(y)
do.call(graphics::points, c(list(x[ind], y[ind]), min))
}
if (!is.null(max) && length(max)) {
+ if (!"xpd" %in% names(max)) max$xpd <- NA
ind <- which.max(y)
do.call(graphics::points, c(list(x[ind], y[ind]), max))
}
diff --git a/man/spec_boxplot.Rd b/man/spec_boxplot.Rd
index 1591f5d..ebde455 100644
--- a/man/spec_boxplot.Rd
+++ b/man/spec_boxplot.Rd
@@ -46,7 +46,7 @@
\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}.}
+and \code{ylim} are used in \code{spec_plot}.}
\item{xaxt}{On/Off for xaxis text}
diff --git a/man/spec_line.Rd b/man/spec_plot.Rd
similarity index 67%
rename from man/spec_line.Rd
rename to man/spec_plot.Rd
index 8d881d1..1917de1 100644
--- a/man/spec_line.Rd
+++ b/man/spec_plot.Rd
@@ -1,10 +1,10 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/mini_plots.R
-\name{spec_line}
-\alias{spec_line}
+\name{spec_plot}
+\alias{spec_plot}
\title{Helper functions to generate inline sparklines}
\usage{
-spec_line(
+spec_plot(
x,
y = NULL,
width = 200,
@@ -20,7 +20,11 @@
border = NULL,
frame.plot = FALSE,
lwd = 2,
- minmax = list(pch = ".", cex = lwd, col = "red"),
+ pch = ".",
+ cex = 2,
+ type = "l",
+ polymin = NA,
+ minmax = list(pch = ".", cex = cex, col = "red"),
min = minmax,
max = minmax,
dir = if (is_latex()) rmd_files_dir() else tempdir(),
@@ -54,14 +58,27 @@
\item{border}{Color for the border.}
-\item{frame.plot}{On/Off for surrounding box (\code{spec_line} only). Default
+\item{frame.plot}{On/Off for surrounding box (\code{spec_plot} only). Default
is False.}
-\item{lwd}{Line width for \code{spec_line}; within \code{spec_line}, the \code{minmax}
+\item{lwd}{Line width for \code{spec_plot}; within \code{spec_plot}, 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 (and 'type' is ignored). Note that if 'polymin' is in the middle
+of the 'y' values, it will generate up/down polygons around this value.}
+
\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
+and maximum values in \code{spec_plot}. 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.}
\item{dir}{Directory of where the images will be saved.}
diff --git a/vignettes/awesome_table_in_html.Rmd b/vignettes/awesome_table_in_html.Rmd
index cb7b1e3..a98ae10 100644
--- a/vignettes/awesome_table_in_html.Rmd
+++ b/vignettes/awesome_table_in_html.Rmd
@@ -235,17 +235,24 @@
c("kableExtra_sm.png", "kableExtra_sm.png"), 50, 50))
```
-`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.
+`kableExtra` also provides a few inline plotting tools. Right now, there are `spec_hist`, `spec_boxplot`, and `spec_plot`. 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 = "", mpg_line = "")
-inline_plot %>%
- kbl(booktabs = T) %>%
- kable_paper(full_width = F) %>%
+disp_list <- split(mtcars$disp, mtcars$cyl)
+inline_plot <- data.frame(cyl = c(4, 6, 8), mpg_box = "", mpg_hist = "",
+ mpg_line1 = "", mpg_line2 = "",
+ mpg_points1 = "", mpg_points2 = "", mpg_poly = "")
+inline_plot %>%
+ kbl(booktabs = TRUE) %>%
+ kable_paper(full_width = FALSE) %>%
column_spec(2, image = spec_boxplot(mpg_list)) %>%
column_spec(3, image = spec_hist(mpg_list)) %>%
- column_spec(4, image = spec_line(mpg_list, same_lim = FALSE))
+ column_spec(4, image = spec_plot(mpg_list, same_lim = TRUE)) %>%
+ column_spec(5, image = spec_plot(mpg_list, same_lim = FALSE)) %>%
+ column_spec(6, image = spec_plot(mpg_list, type = "p")) %>%
+ column_spec(7, image = spec_plot(mpg_list, disp_list, type = "p")) %>%
+ column_spec(8, image = spec_plot(mpg_list, polymin = 5))
```
## Row spec
diff --git a/vignettes/awesome_table_in_pdf.Rmd b/vignettes/awesome_table_in_pdf.Rmd
index 2fd3b25..0c7791a 100644
--- a/vignettes/awesome_table_in_pdf.Rmd
+++ b/vignettes/awesome_table_in_pdf.Rmd
@@ -263,17 +263,24 @@
c("kableExtra_sm.png", "kableExtra_sm.png"), 50, 50))
```
-`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.
+`kableExtra` also provides a few inline plotting tools. Right now, there are `spec_hist`, `spec_boxplot`, and `spec_plot`. 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 = "", mpg_line = "")
-inline_plot %>%
- kbl(booktabs = T) %>%
- kable_paper(full_width = F) %>%
+disp_list <- split(mtcars$disp, mtcars$cyl)
+inline_plot <- data.frame(cyl = c(4, 6, 8), mpg_box = "", mpg_hist = "",
+ mpg_line1 = "", mpg_line2 = "",
+ mpg_points1 = "", mpg_points2 = "", mpg_poly = "")
+inline_plot %>%
+ kbl(booktabs = TRUE) %>%
+ kable_paper(full_width = FALSE) %>%
column_spec(2, image = spec_boxplot(mpg_list)) %>%
column_spec(3, image = spec_hist(mpg_list)) %>%
- column_spec(4, image = spec_line(mpg_list, same_lim = FALSE))
+ column_spec(4, image = spec_plot(mpg_list, same_lim = TRUE)) %>%
+ column_spec(5, image = spec_plot(mpg_list, same_lim = FALSE)) %>%
+ column_spec(6, image = spec_plot(mpg_list, type = "p")) %>%
+ column_spec(7, image = spec_plot(mpg_list, disp_list, type = "p")) %>%
+ column_spec(8, image = spec_plot(mpg_list, polymin = 5))
```
## Row spec