Merge branch 'master' of github.com:haozhu233/kableExtra
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/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 91f9777..bd170bf 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/graphics_helpers.R b/R/graphics_helpers.R
new file mode 100644
index 0000000..83c113a
--- /dev/null
+++ b/R/graphics_helpers.R
@@ -0,0 +1,219 @@
+#' Helper functions to use various graphics devices
+#'
+#' These helper functions generalize the use of strings (e.g.,
+#' `"svg"`, `"pdf"`) or graphic device functions (e.g.,
+#' `grDevices::svg`, `grDevices::pdf`) for in-table plots.
+#'
+#' @param filename Passed through to the graphics device.
+#' @param width,height Plot dimensions in pixels.
+#' @param res The resolution of the plot; default is 300.
+#' @param ... extra parameters passing to the graphics-device function.
+#' @param dev Character (e.g., "svg", "pdf") or function (e.g.,
+#' `grDevices::svg`, `grDevices::pdf`).
+#' @name graphics_helpers
+NULL
+
+#' @describeIn graphics_helpers Generalize 'res' and 'filename across dev functions
+#' @details
+#' - `graphics_dev` generalizes the use of 'res' and plot dimensions
+#' across graphic devices. Raster-based devices (e.g., 'png',
+#' 'jpeg', 'tiff', 'bmp') tend to use 'res' and the width/height
+#' units default to pixels. All other devices (e.g., 'pdf', 'svg')
+#' tend to use inches as the default units for width/height, and
+#' error when 'res' is provided.
+#'
+#' The current heuristic is the look for the 'res' argument in the
+#' function's formals; if that is present, then it is assumed that
+#' the default units are in pixels, so 'width', 'height', and 'res'
+#' are passed through unmodified. If 'res' is not present, then
+#' 'width' and 'height' are converted from pixels to inches, and
+#' 'res' is not passed to the function
+#'
+#' Another purpose of this function is to generalize the different
+#' graphic functions' use of 'file=' versus 'filename='.
+#' @return 'graphics_dev': nothing, a plot device is opened
+graphics_dev <- function(filename, width, height, res, ..., dev) {
+ dev <- match.fun(dev)
+ frmls <- names(formals(dev))
+ dots <- list(...)
+ if ("res" %in% frmls) {
+ dots <- c(dots, list(width = width, height = height, res = res))
+ } else {
+ dots <- c(dots, list(width = width / res, height = height / res))
+ }
+ filenames <- c("file", "filename")
+ found <- na.omit(match(frmls, filenames))
+ if (length(found)) {
+ dots <- c(dots, setNames(filename, filenames[ found ]))
+ } else {
+ stop("could not find a 'file' argument in graphics dev")
+ }
+ do.call(dev, dots)
+}
+
+#' @describeIn graphics_helpers Determine if plot device is svg-like
+#' @details
+#' - `is_svg` determines if the plot device is svg-like, typically one
+#' of `"svg", `grDevices::svg`, or `svglite::svglite`
+#' @return 'is_svg': logical
+is_svg <- function(dev) {
+ if (is.character(dev)) {
+ return(grepl("svg", dev))
+ }
+ if (is.function(dev)) {
+ return(any(sapply(formals(dev), function(f) {
+ tryCatch(any(grepl("svg", as.character(f))),
+ error = function(e) FALSE)
+ })))
+ }
+ stop("unrecognized graphics 'dev': ", paste(class(dev), collapse = ","))
+}
+
+#' @describeIn graphics_helpers Determine filename extension
+#' @details
+#'
+#' - `dev_chr` determines the filename extension for the applicable
+#' plot function; when `dev` is a string, then it is returned
+#' unchanged; when `dev` is a function, the formals of the function
+#' are checked for clues (i.e., default value of a `file=` argument)
+#' @return `dev_chr`: character
+#' @importFrom tools file_ext
+dev_chr <- function(dev) {
+ ext <- ""
+ if (is.character(dev)) {
+ ext <- if (dev == "svglite") "svg" else dev
+ } else if (is.function(dev)) {
+ frmls <- formals(dev)
+ filearg <- grep("^file(name)?$", names(frmls), value = TRUE)
+ if (length(filearg)) {
+ ext <- grep("\\.[[:alpha:]]+$", unlist(sapply(frmls[filearg], as.character)),
+ value = TRUE)
+ ext <- unique(tools::file_ext(ext))[1]
+ }
+ }
+ if (is.na(ext) || !nzchar(ext)) {
+ warning("could not determine filename extension from graphic device")
+ ext <- ""
+ }
+ return(ext)
+}
+
+#' Combine file (or svg text) and parameters into a 'kableExtraInlinePlots' object
+#'
+#' @param filename Passed through to the graphics device.
+#' @param file_ext Character, something like "png".
+#' @param dev Character (e.g., "svg", "pdf") or function (e.g.,
+#' @param width,height Plot dimensions in pixels.
+#' @param res The resolution of the plot; default is 300.
+#' @param del If the file is svg-like, then the default action is to
+#' read the file into an embedded SVG object; once done, the file is
+#' no longer used. The default action is to delete this file early,
+#' set this to 'FALSE' to keep the file.
+#' @return list object, with class 'kableExtraInlinePlots'
+make_inline_plot <- function(filename, file_ext, dev,
+ width, height, res,
+ del = TRUE) {
+ if (is_latex() && (is_svg(file_ext) || is_svg(dev))) {
+ svg_xml <- xml2::read_xml(filename)
+ svg_text <- as.character(svg_xml)
+ if (del) {
+ unlink(filename)
+ filename <- character(0)
+ }
+ } else {
+ if (!is_latex()) {
+ filename <- paste0("file:///", normalizePath(filename, winslash = "/"))
+ }
+ svg_text <- NULL
+ }
+ out <- list(path = filename, dev = file_ext, type = "line",
+ width = width, height = height, res = res,
+ svg_text = svg_text)
+ class(out) <- c("kableExtraInlinePlots", "list")
+ return(out)
+}
+
+#' Convert arguments for a single call into Map-able args
+#'
+#' @param ... Arbitrary arguments to be possibly converted into lists
+#' of arguments.
+#' @param lengths Allowable lengths of the arguments, typically 1 and
+#' the length of the main variable (e.g., "x"). If 'NA' (default),
+#' it is not enforced.
+#' @param passthru Character vector of variables to pass through with
+#' no conversion to lists of values. Extra names (not provided in
+#' `...`) are ignored.
+#' @param notlen1vec Character vector of variables that are known to
+#' be length over 1 for a single plot call, so it will always be
+#' list-ified and extra care to ensure it is grouped correctly.
+#' Extra names (not provided in `...`) are ignored.
+#' @param notlen1lst Character vector of variables that are lists, so
+#' the inner list length is not checked/enforced. (For example, if a
+#' single plot call takes an argument `list(a=1,b=2,d=3)` and the
+#' multi-data call creates three plots, then a naive match might
+#' think that the first plot would get `list(a=1)`, second plot gets
+#' `list(b=2)`, etc. Adding that list-argument to this 'notlen1lst'
+#' will ensure that the full list is passed correctly.) Extra names
+#' (not provided in `...`) are ignored.
+#' @param ignore Character vector of variables to ignore, never
+#' returned. (Generally one can control this by not adding the
+#' variable in the first place, but having this here allows some
+#' sanity checks and/or programmatic usage.)
+#' @return list, generally a list of embedded lists
+listify_args <- function(..., lengths = NA,
+ passthru = c("x", "y"),
+ notlen1vec = c("lim", "xlim", "ylim"),
+ notlen1lst = c("minmax", "min", "max"),
+ ignore = c("same_lim")) {
+ indots <- list(...)
+ dotnms <- sapply(match.call(expand.dots=FALSE)$..., deparse)
+ neednames <- if (is.null(names(indots))) {
+ rep(TRUE, length(indots))
+ } else !nzchar(names(indots))
+ if (any(neednames)) {
+ names(indots)[ neednames ] <- dotnms[ neednames ]
+ }
+ dots <- indots[ intersect(names(indots), passthru) ]
+
+ # these are elements that are not typically length-1, so we need to
+ # listify slightly differently
+ nms <- intersect(names(indots), notlen1vec)
+ if (length(nms)) {
+ dots <- c(dots, Map(
+ function(L, nm) {
+ if (is.null(L)) return(list(NULL))
+ if (!is.list(L)) return(list(L))
+ if (is.na(lengths) || length(L) %in% lengths) return(L)
+ stop("length of '", nm, "' must be one of: ", paste(lengths, collapse = " or "))
+ }, indots[ nms ], nms))
+ }
+
+ # these are a little special in that the argument must be a list
+ # (regardless of its internal length)
+ nms <- intersect(names(indots), notlen1lst)
+ if (length(nms)) {
+ 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 (is.na(lengths) || length(L) %in% lengths) return(L)
+ stop("length of '", nm, "' must be one of: ", paste(lengths, collapse = " or "))
+ }, indots[ nms ], nms))
+ }
+
+ # the remainder, those that we don't know about explicitly and are
+ # not intentionally ignored
+ nms <- setdiff(names(indots), c(passthru, notlen1vec, notlen1lst, ignore))
+ if (length(nms)) {
+ dots <- c(dots, Map(
+ function(V, nm) {
+ if (is.null(V)) return(list(NULL))
+ if (is.function(V)) return(list(V))
+ if (is.na(lengths) || length(V) %in% lengths) return(V)
+ stop("length of '", nm, "' must be one of: ", paste(lengths, collapse = " or "))
+ }, indots[ nms ], nms))
+ }
+
+ dots
+}
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 e1c65d3..fc07ce5 100644
--- a/R/mini_plots.R
+++ b/R/mini_plots.R
@@ -21,7 +21,9 @@
#' @param border Color for the border.
#' @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
+#' @param file_type Graphic device. Can be character (e.g., `"pdf"`)
+#' or a graphics device function (`grDevices::pdf`). This defaults
+#' to `"pdf"` if the rendering is in LaTeX and `"svg"` otherwise.
#' for HTML output
#' @param ... extra parameters sending to `hist()`
#'
@@ -33,59 +35,52 @@
col = "lightgray", border = NULL,
dir = if (is_latex()) rmd_files_dir() else tempdir(),
file = NULL,
- file_type = if (is_latex()) "png" else "svg", ...) {
+ file_type = if (is_latex()) "pdf" else "svg", ...) {
if (is.list(x)) {
if (same_lim & is.null(lim)) {
lim <- base::range(unlist(x))
}
- return(lapply(x, function(x_) {spec_hist(
- x = x_, width = width, height = height,
- breaks = breaks, same_lim = same_lim, lim = lim,
- xaxt = xaxt, yaxt = yaxt, ann = ann, col = col, border = border,
- dir = dir, file = file, file_type = file_type, ...
- )}))
+
+ dots <- listify_args(x, width, height, res, breaks,
+ lim, xaxt, yaxt, ann, col, border,
+ dir, file, file_type,
+ lengths = c(1, length(x)))
+ return(do.call(Map, c(list(f = spec_hist), dots)))
}
+ if (is.null(x)) return(NULL)
+
if (is.null(lim)) {
lim <- base::range(x)
}
- file_type <- match.arg(file_type, c("svg", "png"))
-
if (!dir.exists(dir)) {
dir.create(dir)
}
+ file_ext <- dev_chr(file_type)
if (is.null(file)) {
- file <- file.path(dir, paste0(
- "hist_", round(as.numeric(Sys.time()) * 1000), ".", file_type))
+ file <- normalizePath(
+ tempfile(pattern = "hist_", tmpdir = dir, fileext = paste0(".", file_ext)),
+ winslash = "/", mustWork = FALSE)
}
- 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')
- }
+ graphics_dev(filename = file, dev = file_type,
+ 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=0.5)
graphics::hist(x, breaks = breaks, xlim = lim, border = border,
xaxt = xaxt, yaxt = yaxt, ann = ann, col = col, ...)
- grDevices::dev.off()
- 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 = "hist",
- width = width, height = height, res = res,
- svg_text = svg_text)
+ grDevices::dev.off(curdev)
- class(out) <- "kableExtraInlinePlots"
+ out <- make_inline_plot(
+ file, file_ext, file_type,
+ width, height, res,
+ del = TRUE)
return(out)
}
@@ -108,7 +103,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)
@@ -119,8 +114,9 @@
#' @param medlwd Boxplot - median line width
#' @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
-#' for HTML output
+#' @param file_type Graphic device. Can be character (e.g., `"pdf"`)
+#' or a graphics device function (`grDevices::pdf`). This defaults
+#' to `"pdf"` if the rendering is in LaTeX and `"svg"` otherwise.
#' @param ... extraparameters passing to boxplot
#'
#' @export
@@ -132,45 +128,44 @@
boxlty = 0, medcol = "red", medlwd = 1,
dir = if (is_latex()) rmd_files_dir() else tempdir(),
file = NULL,
- file_type = if (is_latex()) "png" else "svg", ...) {
+ file_type = if (is_latex()) "pdf" else "svg", ...) {
if (is.list(x)) {
if (same_lim & is.null(lim)) {
lim <- base::range(unlist(x))
}
- return(lapply(x, function(x_) {spec_boxplot(
- x = x_, width = width, height = height,
- add_label = add_label, same_lim = same_lim, lim = lim,
- xaxt = xaxt, yaxt = yaxt, ann = ann,
- col = col, border = border,
- boxlty = boxlty, medcol = medcol, medlwd = medlwd,
- dir = dir, file = file, file_type = file_type, ...
- )}))
+
+ dots <- listify_args(x, width, height, res,
+ add_label, label_digits,
+ lim, xaxt, yaxt, ann, col, border,
+ dir, file, file_type,
+ lengths = c(1, length(x)))
+ return(do.call(Map, c(list(f = spec_boxplot), dots)))
}
+ if (is.null(x)) return(NULL)
+
if (is.null(lim)) {
lim <- base::range(x)
lim[1] <- lim[1] - (lim[2] - lim[1]) / 10
lim[2] <- (lim[2] - lim[1]) / 10 + lim[2]
}
- file_type <- match.arg(file_type, c("svg", "png"))
-
if (!dir.exists(dir)) {
dir.create(dir)
}
+ file_ext <- dev_chr(file_type)
if (is.null(file)) {
- file <- file.path(dir, paste0(
- "hist_", round(as.numeric(Sys.time()) * 1000), ".", file_type))
+ file <- normalizePath(
+ tempfile(pattern = "boxplot_", tmpdir = dir, fileext = paste0(".", file_ext)),
+ winslash = "/", mustWork = FALSE)
}
- 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')
- }
+ graphics_dev(filename = file, dev = file_type,
+ 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, 0))
@@ -187,19 +182,13 @@
graphics::text(x_min, y = 0.6, labels = x_min, cex = 0.5)
graphics::text(x_max, y = 0.6, labels = x_max, cex = 0.5)
}
- grDevices::dev.off()
- 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 = "boxplot",
- width = width, height = height, res = res,
- svg_text = svg_text)
- class(out) <- "kableExtraInlinePlots"
+ grDevices::dev.off(curdev)
+
+ out <- make_inline_plot(
+ file, file_ext, file_type,
+ width, height, res,
+ del = TRUE)
return(out)
}
@@ -235,129 +224,153 @@
#' @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
-#' @param file_type Graphic device. Support `png` or `svg`. SVG is recommended
-#' for HTML output.
+#' @param file_type Graphic device. Can be character (e.g., `"pdf"`)
+#' or a graphics device function (`grDevices::pdf`). This defaults
+#' to `"pdf"` if the rendering is in LaTeX and `"svg"` otherwise.
#' @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()) "pdf" 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))
+
+ dots <- listify_args(x, y = y, width, height, res,
+ xlim, ylim, xaxt, yaxt, ann, col, border, frame.plot,
+ lwd, pch, cex, type, polymin, minmax, min, max,
+ dir, file, file_type,
+ lengths = c(1, lenx))
+
+ 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
-
- file_type <- match.arg(file_type, c("svg", "png"))
+ 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
if (!dir.exists(dir)) {
dir.create(dir)
}
+ file_ext <- dev_chr(file_type)
if (is.null(file)) {
- file <- file.path(dir, paste0(
- "hist_", round(as.numeric(Sys.time()) * 1000), ".", file_type))
+ file <- normalizePath(
+ tempfile(pattern = "plot_", tmpdir = dir, fileext = paste0(".", file_ext)),
+ winslash = "/", mustWork = FALSE)
}
- 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')
- }
+ graphics_dev(filename = file, dev = file_type,
+ 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,
+ 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))
}
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"
+ out <- make_inline_plot(
+ file, file_ext, file_type,
+ width, height, res,
+ del = TRUE)
return(out)
}
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/graphics_helpers.Rd b/man/graphics_helpers.Rd
new file mode 100644
index 0000000..02210af
--- /dev/null
+++ b/man/graphics_helpers.Rd
@@ -0,0 +1,80 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/graphics_helpers.R
+\name{graphics_helpers}
+\alias{graphics_helpers}
+\alias{graphics_dev}
+\alias{is_svg}
+\alias{dev_chr}
+\title{Helper functions to use various graphics devices}
+\usage{
+graphics_dev(filename, width, height, res, ..., dev)
+
+is_svg(dev)
+
+dev_chr(dev)
+}
+\arguments{
+\item{filename}{Passed through to the graphics device.}
+
+\item{width, height}{Plot dimensions in pixels.}
+
+\item{res}{The resolution of the plot; default is 300.}
+
+\item{...}{extra parameters passing to the graphics-device function.}
+
+\item{dev}{Character (e.g., "svg", "pdf") or function (e.g.,
+\code{grDevices::svg}, \code{grDevices::pdf}).}
+}
+\value{
+'graphics_dev': nothing, a plot device is opened
+
+'is_svg': logical
+
+\code{dev_chr}: character
+}
+\description{
+These helper functions generalize the use of strings (e.g.,
+\code{"svg"}, \code{"pdf"}) or graphic device functions (e.g.,
+\code{grDevices::svg}, \code{grDevices::pdf}) for in-table plots.
+}
+\details{
+\itemize{
+\item \code{graphics_dev} generalizes the use of 'res' and plot dimensions
+across graphic devices. Raster-based devices (e.g., 'png',
+'jpeg', 'tiff', 'bmp') tend to use 'res' and the width/height
+units default to pixels. All other devices (e.g., 'pdf', 'svg')
+tend to use inches as the default units for width/height, and
+error when 'res' is provided.
+
+The current heuristic is the look for the 'res' argument in the
+function's formals; if that is present, then it is assumed that
+the default units are in pixels, so 'width', 'height', and 'res'
+are passed through unmodified. If 'res' is not present, then
+'width' and 'height' are converted from pixels to inches, and
+'res' is not passed to the function
+
+Another purpose of this function is to generalize the different
+graphic functions' use of 'file=' versus 'filename='.
+}
+
+\itemize{
+\item \code{is_svg} determines if the plot device is svg-like, typically one
+of \verb{"svg", }grDevices::svg\verb{, or }svglite::svglite`
+}
+
+\itemize{
+\item \code{dev_chr} determines the filename extension for the applicable
+plot function; when \code{dev} is a string, then it is returned
+unchanged; when \code{dev} is a function, the formals of the function
+are checked for clues (i.e., default value of a \verb{file=} argument)
+}
+}
+\section{Functions}{
+\itemize{
+\item \code{graphics_dev}: Generalize 'res' and 'filename across dev functions
+
+\item \code{is_svg}: Determine if plot device is svg-like
+
+\item \code{dev_chr}: Determine filename extension
+}}
+
diff --git a/man/listify_args.Rd b/man/listify_args.Rd
new file mode 100644
index 0000000..20f55cf
--- /dev/null
+++ b/man/listify_args.Rd
@@ -0,0 +1,52 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/graphics_helpers.R
+\name{listify_args}
+\alias{listify_args}
+\title{Convert arguments for a single call into Map-able args}
+\usage{
+listify_args(
+ ...,
+ lengths = NA,
+ passthru = c("x", "y"),
+ notlen1vec = c("lim", "xlim", "ylim"),
+ notlen1lst = c("minmax", "min", "max"),
+ ignore = c("same_lim")
+)
+}
+\arguments{
+\item{...}{Arbitrary arguments to be possibly converted into lists
+of arguments.}
+
+\item{lengths}{Allowable lengths of the arguments, typically 1 and
+the length of the main variable (e.g., "x"). If 'NA' (default),
+it is not enforced.}
+
+\item{passthru}{Character vector of variables to pass through with
+no conversion to lists of values. Extra names (not provided in
+\code{...}) are ignored.}
+
+\item{notlen1vec}{Character vector of variables that are known to
+be length over 1 for a single plot call, so it will always be
+list-ified and extra care to ensure it is grouped correctly.
+Extra names (not provided in \code{...}) are ignored.}
+
+\item{notlen1lst}{Character vector of variables that are lists, so
+the inner list length is not checked/enforced. (For example, if a
+single plot call takes an argument \code{list(a=1,b=2,d=3)} and the
+multi-data call creates three plots, then a naive match might
+think that the first plot would get \code{list(a=1)}, second plot gets
+\code{list(b=2)}, etc. Adding that list-argument to this 'notlen1lst'
+will ensure that the full list is passed correctly.) Extra names
+(not provided in \code{...}) are ignored.}
+
+\item{ignore}{Character vector of variables to ignore, never
+returned. (Generally one can control this by not adding the
+variable in the first place, but having this here allows some
+sanity checks and/or programmatic usage.)}
+}
+\value{
+list, generally a list of embedded lists
+}
+\description{
+Convert arguments for a single call into Map-able args
+}
diff --git a/man/make_inline_plot.Rd b/man/make_inline_plot.Rd
new file mode 100644
index 0000000..5312a7b
--- /dev/null
+++ b/man/make_inline_plot.Rd
@@ -0,0 +1,30 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/graphics_helpers.R
+\name{make_inline_plot}
+\alias{make_inline_plot}
+\title{Combine file (or svg text) and parameters into a 'kableExtraInlinePlots' object}
+\usage{
+make_inline_plot(filename, file_ext, dev, width, height, res, del = TRUE)
+}
+\arguments{
+\item{filename}{Passed through to the graphics device.}
+
+\item{file_ext}{Character, something like "png".}
+
+\item{dev}{Character (e.g., "svg", "pdf") or function (e.g.,}
+
+\item{width, height}{Plot dimensions in pixels.}
+
+\item{res}{The resolution of the plot; default is 300.}
+
+\item{del}{If the file is svg-like, then the default action is to
+read the file into an embedded SVG object; once done, the file is
+no longer used. The default action is to delete this file early,
+set this to 'FALSE' to keep the file.}
+}
+\value{
+list object, with class 'kableExtraInlinePlots'
+}
+\description{
+Combine file (or svg text) and parameters into a 'kableExtraInlinePlots' object
+}
diff --git a/man/spec_boxplot.Rd b/man/spec_boxplot.Rd
index 1591f5d..820ade8 100644
--- a/man/spec_boxplot.Rd
+++ b/man/spec_boxplot.Rd
@@ -23,7 +23,7 @@
medlwd = 1,
dir = if (is_latex()) rmd_files_dir() else tempdir(),
file = NULL,
- file_type = if (is_latex()) "png" else "svg",
+ file_type = if (is_latex()) "pdf" else "svg",
...
)
}
@@ -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}
@@ -68,8 +68,9 @@
\item{file}{File name. If not provided, a random name will be used}
-\item{file_type}{Graphic device. Support \code{png} or \code{svg}. SVG is recommended
-for HTML output}
+\item{file_type}{Graphic device. Can be character (e.g., \code{"pdf"})
+or a graphics device function (\code{grDevices::pdf}). This defaults
+to \code{"pdf"} if the rendering is in LaTeX and \code{"svg"} otherwise.}
\item{...}{extraparameters passing to boxplot}
}
diff --git a/man/spec_hist.Rd b/man/spec_hist.Rd
index ae97578..899dcaa 100644
--- a/man/spec_hist.Rd
+++ b/man/spec_hist.Rd
@@ -19,7 +19,7 @@
border = NULL,
dir = if (is_latex()) rmd_files_dir() else tempdir(),
file = NULL,
- file_type = if (is_latex()) "png" else "svg",
+ file_type = if (is_latex()) "pdf" else "svg",
...
)
}
@@ -52,7 +52,9 @@
\item{file}{File name. If not provided, a random name will be used}
-\item{file_type}{Graphic device. Support \code{png} or \code{svg}. SVG is recommended
+\item{file_type}{Graphic device. Can be character (e.g., \code{"pdf"})
+or a graphics device function (\code{grDevices::pdf}). This defaults
+to \code{"pdf"} if the rendering is in LaTeX and \code{"svg"} otherwise.
for HTML output}
\item{...}{extra parameters sending to \code{hist()}}
diff --git a/man/spec_line.Rd b/man/spec_plot.Rd
similarity index 60%
rename from man/spec_line.Rd
rename to man/spec_plot.Rd
index 8d881d1..829bae8 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,12 +20,16 @@
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_type = if (is_latex()) "pdf" else "svg",
...
)
}
@@ -54,22 +58,36 @@
\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.}
\item{file}{File name. If not provided, a random name will be used}
-\item{file_type}{Graphic device. Support \code{png} or \code{svg}. SVG is recommended
-for HTML output.}
+\item{file_type}{Graphic device. Can be character (e.g., \code{"pdf"})
+or a graphics device function (\code{grDevices::pdf}). This defaults
+to \code{"pdf"} if the rendering is in LaTeX and \code{"svg"} otherwise.}
\item{...}{extra parameters passing to \code{plot}}
}
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