diff --git a/NEWS.md b/NEWS.md
index ed086a748..9b5133291 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -4,6 +4,8 @@
* It's now possible to pass array formula vectors to `wb_add_formula()`.
+* `wb_add_data_table()` gained a new `total_row` argument. This allows to add a total row to spreadsheets including text and spreadsheet formulas.
+
## Fixes
* Export `wb_add_ignore_error()`. [955](https://github.com/JanMarvin/openxlsx2/pull/955)
diff --git a/R/class-workbook-wrappers.R b/R/class-workbook-wrappers.R
index 319219a87..773768756 100644
--- a/R/class-workbook-wrappers.R
+++ b/R/class-workbook-wrappers.R
@@ -260,10 +260,31 @@ wb_add_data <- function(
#' @param last_column logical. If `TRUE`, the last column is bold.
#' @param banded_rows logical. If `TRUE`, rows are color banded.
#' @param banded_cols logical. If `TRUE`, the columns are color banded.
+#' @param total_row logical. With the default `FALSE` no total row is added.
#' @param ... additional arguments
#'
+#' @details # Modify total row argument
+#' It is possible to further tweak the total row. In addition to the default
+#' `FALSE` possible values are `TRUE` (the xlsx file will create column sums
+#' each variable).
+#'
+#' In addition it is possible to tweak this further using a character string
+#' with one of the following functions for each variable: `"average"`,
+#' `"count"`, `"countNums"`, `"max"`, `"min"`, `"stdDev"`, `"sum"`, `"var"`.
+#' It is possible to leave the cell empty `"none"` or to create a text input
+#' using a named character with name `text` like: `c(text = "Total")`.
+#' It's also possible to pass other spreadsheet software functions if they
+#' return a single value and hence `"SUM"` would work too.
+#'
#' @family worksheet content functions
#' @family workbook wrappers
+#' @examples
+#' wb <- wb_workbook()$add_worksheet()$
+#' add_data_table(
+#' x = as.data.frame(USPersonalExpenditure),
+#' row_names = TRUE,
+#' total_row = c(text = "Total", "none", "sum", "sum", "sum", "SUM")
+#' )
#' @export
wb_add_data_table <- function(
wb,
@@ -286,6 +307,7 @@ wb_add_data_table <- function(
remove_cell_style = FALSE,
na.strings = na_strings(),
inline_strings = TRUE,
+ total_row = FALSE,
...
) {
assert_workbook(wb)
@@ -309,6 +331,7 @@ wb_add_data_table <- function(
remove_cell_style = remove_cell_style,
na.strings = na.strings,
inline_strings = inline_strings,
+ total_row = total_row,
... = ...
)
}
@@ -530,7 +553,7 @@ wb_add_slicer <- function(
#' cells (see the `MMULT()` example below). For this type of formula, the
#' output range must be known a priori and passed to `dims`, otherwise only the
#' value of the first cell will be returned. This type of formula, whose result
-#' extends over several cells, is only possible with scalar values. If a vector
+#' extends over several cells, is only possible with single strings. If a vector
#' is passed, it is only possible to return individual cells.
#'
#' @param wb A Workbook object containing a worksheet.
diff --git a/R/class-workbook.R b/R/class-workbook.R
index d90126826..bed945f2e 100644
--- a/R/class-workbook.R
+++ b/R/class-workbook.R
@@ -1339,6 +1339,7 @@ wbWorkbook <- R6::R6Class(
#' @param na.strings Value used for replacing `NA` values from `x`. Default
#' `na_strings()` uses the special `#N/A` value within the workbook.
#' @param inline_strings write characters as inline strings
+ #' @param total_row write total rows to table
#' @param ... additional arguments
#' @return The `wbWorkbook` object
add_data_table = function(
@@ -1361,6 +1362,7 @@ wbWorkbook <- R6::R6Class(
remove_cell_style = FALSE,
na.strings = na_strings(),
inline_strings = TRUE,
+ total_row = FALSE,
...
) {
@@ -1394,7 +1396,8 @@ wbWorkbook <- R6::R6Class(
applyCellStyle = apply_cell_style,
removeCellStyle = remove_cell_style,
na.strings = na.strings,
- inline_strings = inline_strings
+ inline_strings = inline_strings,
+ total_row = total_row
)
invisible(self)
},
@@ -2754,23 +2757,25 @@ wbWorkbook <- R6::R6Class(
#' @param tableName tableName
#' @param withFilter withFilter
#' @param totalsRowCount totalsRowCount
+ #' @param totalLabel totalLabel
#' @param showFirstColumn showFirstColumn
#' @param showLastColumn showLastColumn
#' @param showRowStripes showRowStripes
#' @param showColumnStripes showColumnStripes
#' @return The `wbWorksheet` object, invisibly
buildTable = function(
- sheet = current_sheet(),
+ sheet = current_sheet(),
colNames,
ref,
showColNames,
tableStyle,
tableName,
- withFilter, # TODO set default for withFilter?
- totalsRowCount = 0,
- showFirstColumn = 0,
- showLastColumn = 0,
- showRowStripes = 1,
+ withFilter = TRUE,
+ totalsRowCount = 0,
+ totalLabel = FALSE,
+ showFirstColumn = 0,
+ showLastColumn = 0,
+ showRowStripes = 1,
showColumnStripes = 0
) {
@@ -2799,32 +2804,65 @@ wbWorkbook <- R6::R6Class(
}
if (is.null(self$tables)) {
- nms <- NULL
+ nms <- NULL
tSheets <- NULL
- tNames <- NULL
+ tNames <- NULL
tActive <- NULL
} else {
- nms <- self$tables$tab_ref
+ nms <- self$tables$tab_ref
tSheets <- self$tables$tab_sheet
- tNames <- self$tables$tab_name
+ tNames <- self$tables$tab_name
tActive <- self$tables$tab_act
}
### autofilter
autofilter <- if (withFilter) {
- xml_node_create(xml_name = "autoFilter", xml_attributes = c(ref = ref))
+ if (!isFALSE(totalsRowCount)) {
+ # exclude total row from filter
+ rowcol <- dims_to_rowcol(ref)
+ autofilter_ref <- rowcol_to_dims(as.integer(rowcol[[2]])[-length(rowcol[[2]])], rowcol[[1]])
+ } else {
+ autofilter_ref <- ref
+ }
+ xml_node_create(xml_name = "autoFilter", xml_attributes = c(ref = autofilter_ref))
+ }
+
+ trf <- NULL
+ has_total_row <- FALSE
+ has_total_lbl <- FALSE
+ if (!isFALSE(totalsRowCount)) {
+ trf <- totalsRowCount
+ has_total_row <- TRUE
+
+ if (length(totalLabel) == length(colNames)) {
+ lbl <- totalLabel
+ has_total_lbl <- all(is.na(totalLabel))
+ } else {
+ lbl <- rep(NA_character_, length(colNames))
+ has_total_lbl <- FALSE
+ }
}
### tableColumn
tableColumn <- sapply(colNames, function(x) {
id <- which(colNames %in% x)
- xml_node_create("tableColumn", xml_attributes = c(id = id, name = x))
+ trf_id <- if (has_total_row) trf[[id]] else NULL
+ lbl_id <- if (has_total_lbl && !is.na(lbl[[id]])) lbl[[id]] else NULL
+ xml_node_create(
+ "tableColumn",
+ xml_attributes = c(
+ id = id,
+ name = x,
+ totalsRowFunction = trf_id,
+ totalsRowLabel = lbl_id
+ )
+ )
})
tableColumns <- xml_node_create(
- xml_name = "tableColumns",
- xml_children = tableColumn,
+ xml_name = "tableColumns",
+ xml_children = tableColumn,
xml_attributes = c(count = as.character(length(colNames)))
)
@@ -2849,8 +2887,8 @@ wbWorkbook <- R6::R6Class(
name = tableName,
displayName = tableName,
ref = ref,
- totalsRowCount = totalsRowCount,
- totalsRowShown = "0"
+ totalsRowCount = as_xml_attr(has_total_row),
+ totalsRowShown = as_xml_attr(has_total_row)
#headerRowDxfId="1"
)
diff --git a/R/helper-functions.R b/R/helper-functions.R
index 0ef42864a..deb6b93e2 100644
--- a/R/helper-functions.R
+++ b/R/helper-functions.R
@@ -976,3 +976,94 @@ clone_shared_strings <- function(wb_old, old, wb_new, new) {
# print(sprintf("cloned: %s", length(new_ids)))
}
+
+known_subtotal_funs <- function(x, total, table, row_names = FALSE) {
+
+ # unfortunately x has no row names at this point
+ ncol_x <- ncol(x) + row_names
+ nms_x <- names(x)
+ if (row_names) nms_x <- c("_rowNames_", nms_x)
+
+ fml <- vector("character", ncol_x)
+ atr <- vector("character", ncol_x)
+ lbl <- rep(NA_character_, ncol_x)
+
+ if (isTRUE(total) || all(as.character(total) == "109") || all(total == "sum")) {
+ fml <- paste0("SUBTOTAL(109,", table, "[", names(x), "])")
+ atr <- rep("sum", ncol_x)
+ } else {
+
+ # all get the same total_row value
+ if (length(total) == 1) {
+ total <- rep(total, ncol_x)
+ }
+
+ if (length(total) != ncol_x) {
+ stop("length of total_row and table columns do not match", call. = FALSE)
+ }
+
+ builtinIds <- c("101", "103", "102", "104", "105", "107", "109", "110")
+ builtins <- c("average", "count", "countNums", "max", "min", "stdDev", "sum", "var")
+
+ ttl <- as.character(total)
+
+ for (i in seq_len(ncol_x)) {
+
+ if (any(names(total)[i] == "") && (ttl[i] %in% builtinIds || ttl[i] %in% builtins)) {
+ if (ttl[i] == "101" || ttl[i] == "average") {
+ fml[i] <- paste0("SUBTOTAL(", 101, ",", table, "[", nms_x[i], "])")
+ atr[i] <- "average"
+ } else if (ttl[i] == "102" || ttl[i] == "countNums") {
+ fml[i] <- paste0("SUBTOTAL(", 102, ",", table, "[", nms_x[i], "])")
+ atr[i] <- "countNums"
+ } else if (ttl[i] == "103" || ttl[i] == "count") {
+ fml[i] <- paste0("SUBTOTAL(", 103, ",", table, "[", nms_x[i], "])")
+ atr[i] <- "count"
+ } else if (ttl[i] == "104" || ttl[i] == "max") {
+ fml[i] <- paste0("SUBTOTAL(", 104, ",", table, "[", nms_x[i], "])")
+ atr[i] <- "max"
+ } else if (ttl[i] == "105" || ttl[i] == "min") {
+ fml[i] <- paste0("SUBTOTAL(", 105, ",", table, "[", nms_x[i], "])")
+ atr[i] <- "min"
+ } else if (ttl[i] == "107" || ttl[i] == "stdDev") {
+ fml[i] <- paste0("SUBTOTAL(", 107, ",", table, "[", nms_x[i], "])")
+ atr[i] <- "stdDev"
+ } else if (ttl[i] == "109" || ttl[i] == "sum") {
+ fml[i] <- paste0("SUBTOTAL(", 109, ",", table, "[", nms_x[i], "])")
+ atr[i] <- "sum"
+ } else if (ttl[i] == "110" || ttl[i] == "var") {
+ fml[i] <- paste0("SUBTOTAL(", 110, ",", table, "[", nms_x[i], "])")
+ atr[i] <- "var"
+ }
+
+ } else if (ttl[i] == "0" || ttl[i] == "none") {
+ fml[i] <- ""
+ atr[i] <- "none"
+ } else if (any(names(total)[i] == "text")) {
+ fml[i] <- as_xml_attr(ttl[i])
+ atr[i] <- ""
+ lbl[i] <- as_xml_attr(ttl[i])
+ } else {
+ # works, but in excel the formula is added to tables.xml as a child to the column
+ fml[i] <- paste0(ttl[i], "(", table, "[", nms_x[i], "])")
+ atr[i] <- "custom"
+ }
+
+ }
+
+ }
+
+ # prepare output
+ fml <- as.data.frame(t(fml))
+ names(fml) <- nms_x
+ names(atr) <- nms_x
+ names(lbl) <- nms_x
+
+ # prepare output to be written with formulas
+ for (i in seq_along(fml)) {
+ if (is.na(lbl[[i]])) class(fml[[i]]) <- c("formula", fml[[i]])
+ }
+
+ list(fml, atr, lbl)
+
+}
diff --git a/R/write.R b/R/write.R
index 85291a068..10521d60e 100644
--- a/R/write.R
+++ b/R/write.R
@@ -725,6 +725,7 @@ write_data2 <- function(
#' @param na.strings Value used for replacing `NA` values from `x`. Default
#' `na_strings()` uses the special `#N/A` value within the workbook.
#' @param inline_strings optional write strings as inline strings
+#' @param total_row optional write total rows
#' @noRd
#' @keywords internal
write_data_table <- function(
@@ -750,7 +751,8 @@ write_data_table <- function(
removeCellStyle = FALSE,
data_table = FALSE,
na.strings = na_strings(),
- inline_strings = TRUE
+ inline_strings = TRUE,
+ total_row = FALSE
) {
## Input validating
@@ -873,10 +875,10 @@ write_data_table <- function(
wb$worksheets[[sheet]]$autoFilter <- sprintf('', ref)
- l <- int2col(unlist(coords[, 2]))
+ l <- int2col(unlist(coords[, 2]))
dfn <- sprintf("'%s'!%s", wb$get_sheet_names(escape = TRUE)[sheet], stri_join("$", l, "$", coords[, 1], collapse = ":"))
- dn <- sprintf('%s', sheet - 1L, dfn)
+ dn <- sprintf('%s', sheet - 1L, dfn)
if (!is.null(wbdn <- wb$get_named_regions())) {
@@ -897,40 +899,40 @@ write_data_table <- function(
### End: Only in data --------------------------------------------------------
if (data_table) {
- overwrite_nrows <- 1L
+ overwrite_nrows <- 1L
check_tab_head_only <- FALSE
- error_msg <- "Cannot overwrite existing table with another table"
+ error_msg <- "Cannot overwrite existing table with another table"
} else {
- overwrite_nrows <- colNames
+ overwrite_nrows <- colNames
check_tab_head_only <- TRUE
- error_msg <- "Cannot overwrite table headers. Avoid writing over the header row or see wb_get_tables() & wb_remove_tabless() to remove the table object."
+ error_msg <- "Cannot overwrite table headers. Avoid writing over the header row or see wb_get_tables() & wb_remove_tabless() to remove the table object."
}
## Check not overwriting existing table headers
wb_check_overwrite_tables(
- wb = wb,
- sheet = sheet,
- new_rows = c(startRow, startRow + nRow - 1L + overwrite_nrows),
- new_cols = c(startCol, startCol + nCol - 1L),
+ wb = wb,
+ sheet = sheet,
+ new_rows = c(startRow, startRow + nRow - 1L + overwrite_nrows),
+ new_cols = c(startCol, startCol + nCol - 1L),
check_table_header_only = check_tab_head_only,
- error_msg = error_msg
+ error_msg = error_msg
)
## actual driver, the rest should not create data used for writing
wb <- write_data2(
- wb = wb,
- sheet = sheet,
- data = x,
- name = name,
- colNames = colNames,
- rowNames = rowNames,
- startRow = startRow,
- startCol = startCol,
- applyCellStyle = applyCellStyle,
+ wb = wb,
+ sheet = sheet,
+ data = x,
+ name = name,
+ colNames = colNames,
+ rowNames = rowNames,
+ startRow = startRow,
+ startCol = startCol,
+ applyCellStyle = applyCellStyle,
removeCellStyle = removeCellStyle,
- na.strings = na.strings,
- data_table = data_table,
- inline_strings = inline_strings
+ na.strings = na.strings,
+ data_table = data_table,
+ inline_strings = inline_strings
)
### Beg: Only in datatable ---------------------------------------------------
@@ -950,6 +952,39 @@ write_data_table <- function(
tableName <- wb_validate_table_name(wb, tableName)
}
+ ## write total rows column. this is a formula and needs to be written separately
+ total_fml <- FALSE
+ total_lbl <- FALSE
+ if (!isFALSE(total_row)) {
+
+ total <- known_subtotal_funs(
+ x = x,
+ total = total_row,
+ table = tableName,
+ row_names = rowNames
+ )
+
+ total_row <- total[[1]]
+ total_fml <- total[[2]]
+ total_lbl <- total[[3]]
+
+ wb <- write_data2(
+ wb = wb,
+ sheet = sheet,
+ data = total_row,
+ name = name,
+ colNames = FALSE,
+ rowNames = FALSE,
+ startRow = startRow + nrow(x),
+ startCol = startCol,
+ applyCellStyle = applyCellStyle,
+ removeCellStyle = removeCellStyle,
+ na.strings = na.strings,
+ data_table = data_table,
+ inline_strings = inline_strings
+ )
+ }
+
## If 0 rows append a blank row
cstm_tableStyles <- wb$styles_mgr$tableStyle$name
validNames <- c("none", paste0("TableStyleLight", seq_len(21)), paste0("TableStyleMedium", seq_len(28)), paste0("TableStyleDark", seq_len(11)), cstm_tableStyles)
@@ -972,21 +1007,22 @@ write_data_table <- function(
ref1 <- paste0(int2col(startCol), startRow)
ref2 <- paste0(int2col(startCol + nCol - !rowNames), startRow + nRow)
- ref <- paste(ref1, ref2, sep = ":")
+ ref <- paste(ref1, ref2, sep = ":")
## create table.xml and assign an id to worksheet tables
wb$buildTable(
- sheet = sheet,
- colNames = col_names,
- ref = ref,
- showColNames = colNames,
- tableStyle = tableStyle,
- tableName = tableName,
- withFilter = withFilter,
- totalsRowCount = 0L,
- showFirstColumn = firstColumn,
- showLastColumn = lastColumn,
- showRowStripes = bandedRows,
+ sheet = sheet,
+ colNames = col_names,
+ ref = ref,
+ showColNames = colNames,
+ tableStyle = tableStyle,
+ tableName = tableName,
+ totalLabel = total_lbl,
+ withFilter = withFilter,
+ totalsRowCount = total_fml,
+ showFirstColumn = firstColumn,
+ showLastColumn = lastColumn,
+ showRowStripes = bandedRows,
showColumnStripes = bandedCols
)
}
@@ -1210,6 +1246,7 @@ write_datatable <- function(
remove_cell_style = FALSE,
na.strings = na_strings(),
inline_strings = TRUE,
+ total_row = FALSE,
...
) {
@@ -1238,6 +1275,7 @@ write_datatable <- function(
applyCellStyle = apply_cell_style,
removeCellStyle = remove_cell_style,
na.strings = na.strings,
- inline_strings = inline_strings
+ inline_strings = inline_strings,
+ total_row = total_row
)
}
diff --git a/R/write_xlsx.R b/R/write_xlsx.R
index 08f2b52d2..ad2258314 100644
--- a/R/write_xlsx.R
+++ b/R/write_xlsx.R
@@ -6,12 +6,12 @@
#' columns of `x` with class `Date` or `POSIXt` are automatically
#' styled as dates and datetimes respectively.
#'
-#' @param x An object or a list of objects that can be handled by [wb_add_data()] to write to file
-#' @param file An xlsx file name
+#' @param x An object or a list of objects that can be handled by [wb_add_data()] to write to file.
+#' @param file An optional xlsx file name. If no file is passed, the object is not written to disk and only a workbook object is returned.
#' @param as_table If `TRUE`, will write as a data table, instead of data.
#' @inheritDotParams wb_workbook creator
#' @inheritDotParams wb_add_worksheet sheet grid_lines tab_color zoom
-#' @inheritDotParams wb_add_data_table start_col start_row col_names row_names na.strings
+#' @inheritDotParams wb_add_data_table start_col start_row col_names row_names na.strings total_row
#' @inheritDotParams wb_add_data start_col start_row col_names row_names na.strings
#' @inheritDotParams wb_freeze_pane first_active_row first_active_col first_row first_col
#' @inheritDotParams wb_set_col_widths widths
@@ -49,7 +49,7 @@ write_xlsx <- function(x, file, as_table = FALSE, ...) {
arguments <- c(ls(), "creator", "sheet_name", "grid_lines",
"tab_color", "tab_colour",
"zoom", "header", "footer", "even_header", "even_footer", "first_header",
- "first_footer", "start_col", "start_row",
+ "first_footer", "start_col", "start_row", "total_row",
"col.names", "row.names", "col_names", "row_names", "table_style",
"table_name", "with_filter", "first_active_row", "first_active_col",
"first_row", "first_col", "col_widths", "na.strings",
@@ -259,6 +259,11 @@ write_xlsx <- function(x, file, as_table = FALSE, ...) {
tableStyle <- params$table_style
}
+ totalRow <- FALSE
+ if ("total_row" %in% names(params)) {
+ totalRow <- params$total_row
+ }
+
na.strings <-
if ("na.strings" %in% names(params)) {
params$na.strings
@@ -356,7 +361,8 @@ write_xlsx <- function(x, file, as_table = FALSE, ...) {
table_style = tableStyle[[i]],
table_name = NULL,
with_filter = withFilter[[i]],
- na.strings = na.strings
+ na.strings = na.strings,
+ total_row = totalRow
)
} else {
# TODO add_data()?
@@ -440,6 +446,8 @@ write_xlsx <- function(x, file, as_table = FALSE, ...) {
}
}
- wb_save(wb, file = file, overwrite = overwrite)
+ if (!missing(file))
+ wb_save(wb, file = file, overwrite = overwrite)
+
invisible(wb)
}
diff --git a/inst/WORDLIST b/inst/WORDLIST
index f13c88334..7be0d7e35 100644
--- a/inst/WORDLIST
+++ b/inst/WORDLIST
@@ -195,6 +195,7 @@ posixt
pre
printTitleCols
printTitleRows
+priori
pugi
pugixml
queryTables
@@ -239,6 +240,7 @@ tableStyle
tablename
textLength
th
+totalLabel
totalRow
totalsRowCount
twoCell
diff --git a/man/wbWorkbook.Rd b/man/wbWorkbook.Rd
index 51c003d05..f3b826336 100644
--- a/man/wbWorkbook.Rd
+++ b/man/wbWorkbook.Rd
@@ -579,6 +579,7 @@ add a data table
remove_cell_style = FALSE,
na.strings = na_strings(),
inline_strings = TRUE,
+ total_row = FALSE,
...
)}\if{html}{\out{}}
}
@@ -625,6 +626,8 @@ add a data table
\item{\code{inline_strings}}{write characters as inline strings}
+\item{\code{total_row}}{write total rows to table}
+
\item{\code{...}}{additional arguments}
}
\if{html}{\out{}}
@@ -972,8 +975,9 @@ Build table
showColNames,
tableStyle,
tableName,
- withFilter,
+ withFilter = TRUE,
totalsRowCount = 0,
+ totalLabel = FALSE,
showFirstColumn = 0,
showLastColumn = 0,
showRowStripes = 1,
@@ -1000,6 +1004,8 @@ Build table
\item{\code{totalsRowCount}}{totalsRowCount}
+\item{\code{totalLabel}}{totalLabel}
+
\item{\code{showFirstColumn}}{showFirstColumn}
\item{\code{showLastColumn}}{showLastColumn}
diff --git a/man/wb_add_data_table.Rd b/man/wb_add_data_table.Rd
index 22b20b9e9..31d557f7c 100644
--- a/man/wb_add_data_table.Rd
+++ b/man/wb_add_data_table.Rd
@@ -25,6 +25,7 @@ wb_add_data_table(
remove_cell_style = FALSE,
na.strings = na_strings(),
inline_strings = TRUE,
+ total_row = FALSE,
...
)
}
@@ -77,6 +78,8 @@ columns to a character vector e.g.
\item{inline_strings}{write characters as inline strings}
+\item{total_row}{logical. With the default \code{FALSE} no total row is added.}
+
\item{...}{additional arguments}
}
\description{
@@ -112,6 +115,28 @@ distinction is that the latter creates a table in the worksheet that can be
used for different kind of formulas and can be sorted independently, though
is less flexible than basic cell regions.
}
+\section{Modify total row argument}{
+It is possible to further tweak the total row. In addition to the default
+\code{FALSE} possible values are \code{TRUE} (the xlsx file will create column sums
+each variable).
+
+In addition it is possible to tweak this further using a character string
+with one of the following functions for each variable: \code{"average"},
+\code{"count"}, \code{"countNums"}, \code{"max"}, \code{"min"}, \code{"stdDev"}, \code{"sum"}, \code{"var"}.
+It is possible to leave the cell empty \code{"none"} or to create a text input
+using a named character with name \code{text} like: \code{c(text = "Total")}.
+It's also possible to pass other spreadsheet software functions if they
+return a single value and hence \code{"SUM"} would work too.
+}
+
+\examples{
+wb <- wb_workbook()$add_worksheet()$
+ add_data_table(
+ x = as.data.frame(USPersonalExpenditure),
+ row_names = TRUE,
+ total_row = c(text = "Total", "none", "sum", "sum", "sum", "SUM")
+ )
+}
\seealso{
Other worksheet content functions:
\code{\link{col_widths-wb}},
diff --git a/man/wb_add_formula.Rd b/man/wb_add_formula.Rd
index 370d4dfcf..0816dec40 100644
--- a/man/wb_add_formula.Rd
+++ b/man/wb_add_formula.Rd
@@ -67,7 +67,7 @@ take \code{dims} as a reference. For some formulas, the result will span multipl
cells (see the \code{MMULT()} example below). For this type of formula, the
output range must be known a priori and passed to \code{dims}, otherwise only the
value of the first cell will be returned. This type of formula, whose result
-extends over several cells, is only possible with scalar values. If a vector
+extends over several cells, is only possible with single strings. If a vector
is passed, it is only possible to return individual cells.
}
\examples{
diff --git a/man/write_datatable.Rd b/man/write_datatable.Rd
index b1ab251ce..ba14e8b52 100644
--- a/man/write_datatable.Rd
+++ b/man/write_datatable.Rd
@@ -25,6 +25,7 @@ write_datatable(
remove_cell_style = FALSE,
na.strings = na_strings(),
inline_strings = TRUE,
+ total_row = FALSE,
...
)
}
@@ -77,6 +78,8 @@ columns to a character vector e.g.
\item{inline_strings}{write characters as inline strings}
+\item{total_row}{logical. With the default \code{FALSE} no total row is added.}
+
\item{...}{additional arguments}
}
\description{
diff --git a/man/write_xlsx.Rd b/man/write_xlsx.Rd
index 1f4419b88..d43efd540 100644
--- a/man/write_xlsx.Rd
+++ b/man/write_xlsx.Rd
@@ -7,9 +7,9 @@
write_xlsx(x, file, as_table = FALSE, ...)
}
\arguments{
-\item{x}{An object or a list of objects that can be handled by \code{\link[=wb_add_data]{wb_add_data()}} to write to file}
+\item{x}{An object or a list of objects that can be handled by \code{\link[=wb_add_data]{wb_add_data()}} to write to file.}
-\item{file}{An xlsx file name}
+\item{file}{An optional xlsx file name. If no file is passed, the object is not written to disk and only a workbook object is returned.}
\item{as_table}{If \code{TRUE}, will write as a data table, instead of data.}
@@ -24,6 +24,7 @@ hidden.}
\code{grDevices::colors()}) or a valid hex color beginning with "#".}
\item{\code{zoom}}{The sheet zoom level, a numeric between 10 and 400 as a
percentage. (A zoom value smaller than 10 will default to 10.)}
+ \item{\code{total_row}}{logical. With the default \code{FALSE} no total row is added.}
\item{\code{start_col}}{A vector specifying the starting column to write \code{x} to.}
\item{\code{start_row}}{A vector specifying the starting row to write \code{x} to.}
\item{\code{col_names}}{If \code{TRUE}, column names of \code{x} are written.}
diff --git a/tests/testthat/test-write.R b/tests/testthat/test-write.R
index 2e167f6f8..84b7dd4f3 100644
--- a/tests/testthat/test-write.R
+++ b/tests/testthat/test-write.R
@@ -806,3 +806,74 @@ test_that("filter works with wb_add_data()", {
expect_equal(exp, got)
})
+
+test_that("writing total row works", {
+
+ # default row sums
+ wb <- wb_workbook()$add_worksheet()$add_data_table(x = mtcars, total_row = TRUE)
+
+ exp <- data.frame(
+ A = "SUBTOTAL(109,Table1[mpg])", B = "SUBTOTAL(109,Table1[cyl])",
+ C = "SUBTOTAL(109,Table1[disp])", D = "SUBTOTAL(109,Table1[hp])",
+ E = "SUBTOTAL(109,Table1[drat])", F = "SUBTOTAL(109,Table1[wt])",
+ G = "SUBTOTAL(109,Table1[qsec])", H = "SUBTOTAL(109,Table1[vs])",
+ I = "SUBTOTAL(109,Table1[am])", J = "SUBTOTAL(109,Table1[gear])",
+ K = "SUBTOTAL(109,Table1[carb])"
+ )
+ got <- wb_to_df(wb, dims = wb_dims(rows = 33, cols = "A:K"),
+ show_formula = TRUE, col_names = FALSE)
+ expect_equal(exp, got, ignore_attr = TRUE)
+
+ # empty total row
+ wb <- wb_workbook()$add_worksheet()$add_data_table(x = mtcars, total_row = c("none"))
+
+ exp <- data.frame(
+ A = NA_real_, B = NA_real_, C = NA_real_, D = NA_real_,
+ E = NA_real_, F = NA_real_, G = NA_real_, H = NA_real_, I = NA_real_,
+ J = NA_real_, K = NA_real_
+ )
+ got <- wb_to_df(wb, dims = wb_dims(rows = 33, cols = "A:K"),
+ show_formula = TRUE, col_names = FALSE)
+ expect_equal(exp, got, ignore_attr = TRUE)
+
+ # total row with text only
+ wb <- wb_workbook()$add_worksheet()$add_data_table(x = cars, total_row = c(text = "Result", text = "sum"))
+
+ exp <- data.frame(A = "Result", B = "sum")
+ got <- wb_to_df(wb, dims = wb_dims(rows = 51, cols = "A:B"),
+ show_formula = TRUE, col_names = FALSE)
+ expect_equal(exp, got, ignore_attr = TRUE)
+
+ # total row with text and formula
+ wb <- wb_workbook()$add_worksheet()$add_data_table(x = cars, total_row = c(text = "Result", "sum"))
+
+ exp <- data.frame(A = "Result", B = "SUBTOTAL(109,Table1[dist])")
+ got <- wb_to_df(wb, dims = wb_dims(rows = 51, cols = "A:B"),
+ show_formula = TRUE, col_names = FALSE)
+ expect_equal(exp, got, ignore_attr = TRUE)
+
+ # total row with none and custom formula
+ wb <- wb_workbook()$add_worksheet()$add_data_table(x = cars, total_row = c("none", "COUNTA"))
+
+ exp <- data.frame(A = NA_real_, B = "COUNTA(Table1[dist])")
+ got <- wb_to_df(wb, dims = wb_dims(rows = 51, cols = "A:B"),
+ show_formula = TRUE, col_names = FALSE)
+ expect_equal(exp, got, ignore_attr = TRUE)
+
+ # with rownames
+ wb <- wb_workbook()$add_worksheet()$
+ add_data_table(
+ x = as.data.frame(USPersonalExpenditure),
+ row_names = TRUE,
+ total_row = c(text = "Total", "none", "sum", "sum", "sum", "SUM")
+ )
+
+ exp <- data.frame(
+ A = "Total", B = NA_real_, C = "SUBTOTAL(109,Table1[1945])",
+ D = "SUBTOTAL(109,Table1[1950])", E = "SUBTOTAL(109,Table1[1955])",
+ F = "SUM(Table1[1960])"
+ )
+ got <- wb_to_df(wb, dims = wb_dims(rows = 6, cols = "A:F"), col_names = FALSE, show_formula = TRUE)
+ expect_equal(exp, got, ignore_attr = TRUE)
+
+})