Nothing
formatColumns = function(table, columns, template, ..., appendTo = c('columnDefs', 'rowCallback'), rows = NULL) {
if (!inherits(table, 'datatables'))
stop("Invalid table argument; a table object created from datatable() was expected")
if (inherits(columns, 'formula')) columns = all.vars(columns)
x = table$x
colnames = base::attr(x, 'colnames', exact = TRUE)
rownames = base::attr(x, 'rownames', exact = TRUE)
appendTo = match.arg(appendTo)
if (appendTo == 'columnDefs') {
x$options$columnDefs = append(
# must append to the front so that the later formatting
# can override the previous formatting
x$options$columnDefs, colFormatter(
columns, colnames, rownames, rows, template, ...
), after = 0L
)
} else {
x$options$rowCallback = appendFormatter(
x$options$rowCallback, columns, colnames, rownames, template, ...
)
}
table$x = x
table
}
#' Format table columns
#'
#' Format numeric columns in a table as currency (`formatCurrency()`) or
#' percentages (`formatPercentage()`), or round numbers to a specified
#' number of decimal places (`formatRound()`), or a specified number
#' of significant figures (`formatSignif()`). The function
#' `formatStyle()` applies CSS styles to table cells by column.
#' @param table a table object created from [datatable()]
#' @param columns the indices of the columns to be formatted (can be character,
#' numeric, logical, or a formula of the form `~ V1 + V2`, which is
#' equivalent to `c('V1', 'V2')`)
#' @param currency the currency symbol
#' @param interval put a marker after how many digits of the numbers
#' @param mark the marker after every `interval` decimals in the numbers
#' @param dec.mark a character to indicate the decimal point
#' @param before whether to place the currency symbol before or after the values
#' @param zero.print a string to specify how zeros should be formatted.
#' Useful for when many zero values exist. If `NULL`, keeps zero as it is.
#' @param rows an integer vector (starting from 1) to specify the only rows
#' that the style applies to.
#' By default, it's `NULL`, meaning all rows should be formatted. Note,
#' `formatStyle()` doesn't support this argument and you should use
#' `styleRow()` instead. In addition, this only works expected in the
#' client-side processing mode, i.e., `server = FALSE`.
#' @references See <https://rstudio.github.io/DT/functions.html> for detailed
#' documentation and examples.
#' @note The length of arguments other than `table` should be 1 or the same as
#' the length of `columns`.
#' @export
#' @examples # !formatR
#' library(DT)
#' m = cbind(matrix(rnorm(120, 1e5, 1e6), 40), runif(40), rnorm(40, 100))
#' colnames(m) = head(LETTERS, ncol(m))
#' m
#'
#' # format the columns A and C as currency, and D as percentages
#' datatable(m) %>% formatCurrency(c('A', 'C')) %>% formatPercentage('D', 2)
#'
#' # the first two columns are Euro currency, and round column E to 3 decimal places
#' datatable(m) %>% formatCurrency(1:2, '\U20AC') %>% formatRound('E', 3)
#'
#' # render vapor pressure with only two significant figures.
#' datatable(pressure) %>% formatSignif('pressure',2)
#'
#' # apply CSS styles to columns
#' datatable(iris) %>%
#' formatStyle('Sepal.Length', fontWeight = styleInterval(5, c('bold', 'weight'))) %>%
#' formatStyle('Sepal.Width',
#' color = styleInterval(3.4, c('red', 'white')),
#' backgroundColor = styleInterval(3.4, c('yellow', 'gray'))
#' )
formatCurrency = function(
table, columns, currency = '$', interval = 3, mark = ',', digits = 2,
dec.mark = getOption('OutDec'), before = TRUE, zero.print = NULL, rows = NULL
) {
currency = gsub("'", "\\\\'", currency)
mark = gsub("'", "\\\\'", mark)
formatColumns(table, columns, tplCurrency, currency, interval, mark, digits, dec.mark, before, zero.print, rows = rows)
}
#' @export
#' @rdname formatCurrency
#' @param prefix string to put in front of the column values
#' @param suffix string to put after the column values
formatString = function(table, columns, prefix = '', suffix = '', rows = NULL) {
formatColumns(table, columns, tplString, prefix, suffix, rows = rows)
}
#' @export
#' @rdname formatCurrency
#' @param digits the number of decimal places to round to
formatPercentage = function(
table, columns, digits = 0, interval = 3, mark = ',', dec.mark = getOption('OutDec'), zero.print = NULL, rows = NULL
) {
formatColumns(table, columns, tplPercentage, digits, interval, mark, dec.mark, zero.print, rows = rows)
}
#' @export
#' @rdname formatCurrency
formatRound = function(
table, columns, digits = 2, interval = 3, mark = ',', dec.mark = getOption('OutDec'), zero.print = NULL, rows = NULL
) {
formatColumns(table, columns, tplRound, digits, interval, mark, dec.mark, zero.print, rows = rows)
}
#' @export
#' @rdname formatCurrency
formatSignif = function(
table, columns, digits = 2, interval = 3, mark = ',', dec.mark = getOption('OutDec'), zero.print = NULL, rows = NULL
) {
formatColumns(table, columns, tplSignif, digits, interval, mark, dec.mark, zero.print, rows = rows)
}
#' @export
#' @rdname formatCurrency
#' @param method the method(s) to convert a date to string in JavaScript; see
#' `DT:::DateMethods` for a list of possible methods, and
#' <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date>
#' for a full reference
#' @param params a list parameters for the specific date conversion method,
#' e.g., for the `toLocaleDateString()` method, your browser may support
#' `params = list('ko-KR', list(year = 'numeric', month = 'long', day =
#' 'numeric'))`
formatDate = function(table, columns, method = 'toDateString', params = NULL, rows = NULL) {
formatColumns(table, columns, tplDate, method, params, rows = rows)
}
#' @param valueColumns indices of the columns from which the cell values are
#' obtained; this can be different with the `columns` argument, e.g. you
#' may style one column based on the values of a different column
#' @param target the target to apply the CSS styles to (the current cell or the
#' full row)
#' @param fontWeight the font weight, e.g. `'bold'` and `'normal'`
#' @param color the font color, e.g. `'red'` and `'#ee00aa'`
#' @param backgroundColor the background color of table cells
#' @param background the background of table cells
#' @param ... other CSS properties, e.g. `'border'`, `'font-size'`,
#' `'text-align'`, and so on; if you want to condition CSS styles on the
#' cell values, you may use the helper functions such as
#' [styleInterval()]; note the actual CSS property names are
#' dash-separated, but you can use camelCase names in this function (otherwise
#' you will have to use backticks to quote the names, e.g. ``font-size` =
#' '12px'`), and this function will automatically convert camelCase names to
#' dash-separated names (e.g. `'fontWeight'` will be converted to
#' `'font-weight'` internally)
#' @export
#' @rdname formatCurrency
formatStyle = function(
table, columns, valueColumns = columns, target = c('cell', 'row'),
fontWeight = NULL, color = NULL, backgroundColor = NULL, background = NULL, ...
) {
styles = dropNULL(list(
fontWeight = fontWeight, color = color, backgroundColor = backgroundColor,
background = background, ...
))
formatColumns(table, columns, tplStyle, valueColumns, match.arg(target), styles,
appendTo = 'rowCallback')
}
# turn character/logical indices to numeric indices
name2int = function(name, names, rownames, noerror = FALSE) {
if (is.numeric(name)) {
i = if (all(name >= 0)) name else seq_along(names)[name]
if (!rownames) i = i - 1
return(i)
}
i = unname(setNames(seq_along(names), names)[name]) - 1
if (any(is.na(i))) {
if (!noerror) stop(
'You specified the columns: ', paste(name, collapse = ', '), ', ',
'but the column names of the data are ', paste(names, collapse = ', ')
)
i = na.omit(i)
}
i
}
colFormatter = function(name, names, rownames = TRUE, rows = NULL, template, ...) {
i = name2int(name, names, rownames)
rowJS = ''
if (length(rows)) {
rows = as.integer(rows)
rowJS = sprintf(' || $.inArray(meta.row + 1, %s) < 0', toJSON(rows))
}
# see https://datatables.net/reference/option/columns.render
# #837 we only want to use the formatting for the "display" purpose
js = sprintf("function(data, type, row, meta) {
return type !== 'display'%s ? data : %s
}", rowJS, template(...))
Map(function(i, js) list(targets = i, render = JS(js)), i, js, USE.NAMES = FALSE)
}
appendFormatter = function(js, name, names, rownames = TRUE, template, ...) {
js = if (length(js) == 0) {
c('function(row, data, displayNum, displayIndex, dataIndex) {', '}')
} else {
unlist(strsplit(as.character(js), '\n'))
}
i = name2int(name, names, rownames, noerror = TRUE)
JS(append(
js, after = length(js) - 1,
template(i, ..., names, rownames)
))
}
tplCurrency = function(currency, interval, mark, digits, dec.mark, before, zero.print, ...) {
sprintf(
"DTWidget.formatCurrency(data, %s, %d, %d, %s, %s, %s, %s);",
jsValues(currency), digits, interval, jsValues(mark), jsValues(dec.mark),
jsValues(before), jsValuesHandleNull(zero.print)
)
}
tplString = function(prefix, suffix, ...) {
sprintf(
"DTWidget.formatString(data, %s, %s);",
jsValues(prefix), jsValues(suffix)
)
}
tplPercentage = function(digits, interval, mark, dec.mark, zero.print, ...) {
sprintf(
"DTWidget.formatPercentage(data, %d, %d, %s, %s, %s);",
digits, interval, jsValues(mark), jsValues(dec.mark), jsValuesHandleNull(zero.print)
)
}
tplRound = function(digits, interval, mark, dec.mark, zero.print, ...) {
sprintf(
"DTWidget.formatRound(data, %d, %d, %s, %s, %s);",
digits, interval, jsValues(mark), jsValues(dec.mark), jsValuesHandleNull(zero.print)
)
}
tplSignif = function(digits, interval, mark, dec.mark, zero.print, ...) {
sprintf(
"DTWidget.formatSignif(data, %d, %d, %s, %s, %s);",
digits, interval, jsValues(mark), jsValues(dec.mark), jsValuesHandleNull(zero.print)
)
}
tplDate = function(method, params, ...) {
params = if (length(params) > 0) paste(',', toJSON(params)) else ''
sprintf("DTWidget.formatDate(data, %s%s);", jsValues(method), params)
}
DateMethods = c(
'toDateString', 'toISOString', 'toLocaleDateString', 'toLocaleString',
'toLocaleTimeString', 'toString', 'toTimeString', 'toUTCString'
)
tplStyle = function(cols, valueCols, target, styles, ...) {
if (length(styles) == 0) return()
if (!is.list(styles)) stop("'styles' must be a list")
JSclass = class(JS(''))
# if a style is specified in JS(), do not single quote it (it is a JS expression)
styles = vapply(styles, function(s) {
isJS = inherits(s, JSclass)
s = paste(s, collapse = '\n')
if (isJS) s else sprintf("'%s'", s)
}, FUN.VALUE = character(1))
css = paste(sprintf("'%s':%s", upperToDash(names(styles)), styles), collapse = ',')
valueCols = name2int(valueCols, ...)
switch(
target,
cell = sprintf(
"var value=data[%s]; $(this.api().cell(row, %s).node()).css({%s});",
valueCols, cols, css
),
row = sprintf(
"var value=data[%s]; $(row).css({%s});",
valueCols, css
),
stop('Invalid target!')
)
}
jsValues = function(x) {
if (inherits(x, c("POSIXt", "POSIXct", "POSIXlt"))) {
x = format(x, "%Y-%m-%dT%H:%M:%SZ", tz = "UTC")
} else if (inherits(x, "Date")) {
x = format(x, "%Y-%m-%d")
}
vapply(x, jsonlite::toJSON, character(1), auto_unbox = TRUE)
}
jsValuesHandleNull = function(x) {
if (is.null(x)) {
"null"
} else {
jsValues(x)
}
}
#' Conditional CSS styles
#'
#' A few helper functions for the [formatStyle()] function to
#' calculate CSS styles for table cells based on the cell values. Under the
#' hood, they just generate JavaScript and CSS code from the values specified in
#' R.
#'
#' The function `styleInterval()` maps intervals to CSS values. Its
#' argument `values` must be of length `n + 1` where `n =
#' length(cuts)`. The right-closed interval \samp{(cuts[i - 1], cuts[i]]} is
#' mapped to \samp{values[i]} for \samp{i = 2, 3, ..., n}; \samp{values[1]} is
#' for the interval \samp{(-Inf, cuts[1]]}, and \samp{values[n + 1]} is for
#' \samp{(cuts[n], +Inf)}. You can think of the order of `cuts` and
#' `values` using this diagram: \samp{-Inf -> values[1] -> cuts[1] ->
#' values[2] -> cuts[2] -> ... -> values[n] -> cuts[n] -> values[n + 1] ->
#' +Inf}.
#'
#' The function `styleEqual()` maps data values to CSS values in the
#' one-to-one manner, i.e. `values[i]` is used when the table cell value is
#' `levels[i]`.
#'
#' The function `styleColorBar()` can be used to draw background color bars
#' behind table cells in a column, and the width of bars is proportional to the
#' column values.
#'
#' The function `styleValue()` uses the column value as the CSS values.
#'
#' The function `styleRow()` applies the CSS values based on Row Indexes.
#' This only works expected in the client-side processing mode, i.e., `server = FALSE`.
#'
#' @param cuts a vector of cut points (sorted increasingly)
#' @param values a vector of CSS values
#' @export
styleInterval = function(cuts, values) {
n = length(cuts)
if (n != length(values) - 1)
stop("length(cuts) must be equal to length(values) - 1")
values = jsValues(values)
if (n == 0) return(values)
if (!all(cuts == sort(cuts))) stop("'cuts' must be sorted increasingly")
js = "isNaN(parseFloat(value)) ? '' : " # missing or non-numeric values in data
cuts = jsValues(cuts)
for (i in seq_len(n)) {
js = paste0(js, sprintf('value <= %s ? %s : ', cuts[i], values[i]))
}
JS(paste0(js, values[n + 1]))
}
#' @param levels a character vector of data values to be mapped (one-to-one) to
#' CSS values
#' @param default a string or `NULL` used as the the default CSS value
#' for values other than levels. If `NULL`, the CSS value of non-matched
#' cells will be left unchanged.
#' @export
#' @rdname styleInterval
styleEqual = function(levels, values, default = NULL) {
n = length(levels)
if (length(values) == 1L) values <- rep(values, n)
if (n != length(values))
stop("length(levels) must be equal to length(values) when `values` is not a scalar")
if (!is.null(default) && (!is.character(default) || length(default) != 1))
stop("default must be null or a string")
if (n == 0) return("''")
# because the data will be transformed by escapeData(), we need to compare
# the "escaped value" of the "levels" instead of the raw value
if (is.character(levels) || is.factor(levels))
levels = htmlEscape(levels)
levels = jsValues(levels)
values = jsValues(values)
js = ''
for (i in seq_len(n)) {
js = paste0(js, sprintf("value == %s ? %s : ", levels[i], values[i]))
}
# set the css to null will leave the attribute as it is. Despite it's not
# documented explicitly but the jquery test covers this behavior
# https://github.com/jquery/jquery/commit/2ae872c594790c4b935a1d7eabdf8b8212fd3c3f
default = jsValuesHandleNull(default)
JS(paste0(js, default))
}
#' @export
#' @rdname styleInterval
styleValue = function() {
JS('value')
}
#' @param data a numeric vector whose range will be used for scaling the
#' table data from 0-100 before being represented as color bars. A vector
#' of length 2 is acceptable here for specifying a range possibly wider or
#' narrower than the range of the table data itself.
#' @param color the color of the bars
#' @param angle a number of degrees representing the direction to fill the
#' gradient relative to a horizontal line and the gradient line, going
#' counter-clockwise. For example, 90 fills right to left and -90 fills
#' left to right.
#' @export
#' @rdname styleInterval
styleColorBar = function(data, color, angle=90) {
rg = range(data, na.rm = TRUE, finite = TRUE)
r1 = rg[1]; r2 = rg[2]; r = r2 - r1
JS(sprintf(
"isNaN(parseFloat(value)) || value <= %f ? '' : 'linear-gradient(%fdeg, transparent ' + Math.max(%f - value, 0)/%f * 100 + '%%, %s ' + Math.max(%f - value, 0)/%f * 100 + '%%)'",
r1, angle, r2, r, color, r2, r
))
}
#' @param rows the Row Indexes (starting from 1) that applies the CSS style. It could
#' be an integer vector or a list of integer vectors, whose length must be equal to
#' the length of `values`, when `values` is not a scalar.
#' @rdname styleInterval
#' @export
styleRow = function(rows, values, default = NULL) {
n = length(rows)
if (length(values) == 1L) values <- rep(values, n)
if (n != length(values))
stop("length(rows) must be equal to length(values) when `values` is not a scalar")
if (!is.null(default) && (!is.character(default) || length(default) != 1))
stop("default must be null or a string")
if (n == 0) return("''")
values = jsValues(values)
js = ''
for (i in seq_len(n)) {
row = as.integer(rows[[i]])
# must use dataIndex + 1 as it's suppse
js = paste0(js, sprintf("$.inArray(dataIndex + 1, [%s]) >= 0 ? %s : ", toString(row), values[i]))
}
default = jsValuesHandleNull(default)
JS(paste0(js, default))
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.