From 2e4d10bf3879d4de817840df8530e3a06c53e2f4 Mon Sep 17 00:00:00 2001 From: Jan Marvin Garbuszus Date: Fri, 14 Jul 2023 16:57:55 +0200 Subject: [PATCH 1/4] provide dims() --- NAMESPACE | 1 + R/utils.R | 39 +++++++++++++++++++++++++++++++++++++ man/dims_helper.Rd | 5 +++++ tests/testthat/test-utils.R | 17 ++++++++++++++++ 4 files changed, 62 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index 7b0eda434..7b6d610c1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -27,6 +27,7 @@ export(create_tablestyle) export(current_sheet) export(dataframe_to_dims) export(delete_data) +export(dims) export(dims_to_dataframe) export(dims_to_rowcol) export(fmt_txt) diff --git a/R/utils.R b/R/utils.R index 610f3c4b5..f6a9e4cdd 100644 --- a/R/utils.R +++ b/R/utils.R @@ -259,6 +259,45 @@ rowcol_to_dim <- function(row, col) { # we will always return something like "A1" stringi::stri_join(min_col, min_row) } + +#' @rdname dims_helper +#' @param ... dims arguments, row/col, rows/cols or objects that can be converted to data frame +#' @export +dims <- function(...) { + + args <- list(...) + nams <- names(args) + + col_names <- args$col_names + row_names <- args$row_names + + if (is.null(col_names)) col_names <- FALSE + if (is.null(row_names)) row_names <- FALSE + + assert_class(col_names, "logical") + assert_class(row_names, "logical") + + if (any("row" %in% nams) && any("col" %in% nams)) { + dims <- rowcol_to_dim(args$row, args$col) + } else if (any("rows" %in% nams) && any("cols" %in% nams)) { + dims <- rowcol_to_dims(args$rows, args$cols) + } else { + + x <- as.data.frame(args[[1]]) + rows <- seq_len(nrow(x) + col_names) + cols <- seq_len(ncol(x) + row_names) + + if (length(rows) == 1L && length(cols) == 1L) { + dims <- rowcol_to_dim(rows, cols) + } else { + dims <- rowcol_to_dims(rows, cols) + } + + } + + dims +} + # Relationship helpers -------------------- #' removes entries from worksheets_rels #' @param x character string diff --git a/man/dims_helper.Rd b/man/dims_helper.Rd index 38b16e8c7..6d822f85f 100644 --- a/man/dims_helper.Rd +++ b/man/dims_helper.Rd @@ -4,11 +4,14 @@ \alias{dims_helper} \alias{dims_to_rowcol} \alias{rowcol_to_dims} +\alias{dims} \title{Helper functions to work with \code{dims}} \usage{ dims_to_rowcol(x, as_integer = FALSE) rowcol_to_dims(row, col) + +dims(...) } \arguments{ \item{x}{a dimension object "A1" or "A1:A1"} @@ -18,6 +21,8 @@ rowcol_to_dims(row, col) \item{row}{a numeric vector of rows} \item{col}{a numeric or character vector of cols} + +\item{...}{dims arguments, row/col, rows/cols or objects that can be converted to data frame} } \value{ \itemize{ diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index dc3b4a31f..1a731b292 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -46,6 +46,23 @@ test_that("dims to col & row and back", { }) +test_that("dims() works", { + + # dim(mtcars) + + expect_equal(dims(mtcars), "A1:K32") + + expect_equal(dims(letters), "A1:A26") + + expect_equal(dims(t(letters)), "A1:Z1") + + expect_equal(dims(1), "A1") + + expect_equal(dims(rows = 1:10, cols = 5:7), "E1:G10") + + expect_equal(dims(row = 5, col = 7), "G5") + +}) test_that("create_char_dataframe", { From 21967f1bc059d75fa9f94f6bf775060b97a3fb82 Mon Sep 17 00:00:00 2001 From: Jan Marvin Garbuszus Date: Fri, 14 Jul 2023 19:58:57 +0200 Subject: [PATCH 2/4] change function name to wb_dims() handle some exceptions --- NAMESPACE | 2 +- R/utils.R | 51 +++++++++++++++++++++++++++---------- man/dims_helper.Rd | 10 +++++--- tests/testthat/test-utils.R | 23 ++++++++++++----- 4 files changed, 62 insertions(+), 24 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 7b6d610c1..9429420ee 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -27,7 +27,6 @@ export(create_tablestyle) export(current_sheet) export(dataframe_to_dims) export(delete_data) -export(dims) export(dims_to_dataframe) export(dims_to_rowcol) export(fmt_txt) @@ -77,6 +76,7 @@ export(wb_color) export(wb_colour) export(wb_copy_cells) export(wb_data) +export(wb_dims) export(wb_freeze_pane) export(wb_get_active_sheet) export(wb_get_base_font) diff --git a/R/utils.R b/R/utils.R index f6a9e4cdd..e0c1ce359 100644 --- a/R/utils.R +++ b/R/utils.R @@ -261,9 +261,14 @@ rowcol_to_dim <- function(row, col) { } #' @rdname dims_helper -#' @param ... dims arguments, row/col, rows/cols or objects that can be converted to data frame +#' @param ... construct dims arguments, from rows/cols vectors or objects that can be coerced to data frame +#' @examples +#' # either vectors +#' wb_dims(rows = 1:10, cols = 1:10) +#' # or objects +#' wb_dims(mtcars) #' @export -dims <- function(...) { +wb_dims <- function(...) { args <- list(...) nams <- names(args) @@ -271,28 +276,48 @@ dims <- function(...) { col_names <- args$col_names row_names <- args$row_names - if (is.null(col_names)) col_names <- FALSE - if (is.null(row_names)) row_names <- FALSE + has_cnam <- is.null(col_names) + has_rnam <- is.null(row_names) + + if (has_cnam) col_names <- FALSE + if (has_rnam) row_names <- FALSE assert_class(col_names, "logical") assert_class(row_names, "logical") - if (any("row" %in% nams) && any("col" %in% nams)) { - dims <- rowcol_to_dim(args$row, args$col) - } else if (any("rows" %in% nams) && any("cols" %in% nams)) { - dims <- rowcol_to_dims(args$rows, args$cols) + # wb_dims(rows, cols) + if (length(args) == 2 && has_cnam && has_rnam) { + rows <- 1L + cols <- 2L + + # wb_dims(rows = rows, cols = cols) + sel <- pmatch(nams, c("rows", "cols")) + valid <- length(sel[!is.na(sel)]) + if (valid == 2) { + rows <- sel[rows] + cols <- sel[cols] + } else if (valid == 1) { + stop("found only one cols/rows argument") + } + + rows <- args[[rows]] + cols <- args[[cols]] + } else { + # wb_dims(data.frame()) x <- as.data.frame(args[[1]]) rows <- seq_len(nrow(x) + col_names) cols <- seq_len(ncol(x) + row_names) - if (length(rows) == 1L && length(cols) == 1L) { - dims <- rowcol_to_dim(rows, cols) - } else { - dims <- rowcol_to_dims(rows, cols) - } + } + if (length(rows) == 1 && length(cols) == 1) { + # A1 + dims <- rowcol_to_dim(rows, cols) + } else { + # A1:B2 + dims <- rowcol_to_dims(rows, cols) } dims diff --git a/man/dims_helper.Rd b/man/dims_helper.Rd index 6d822f85f..21077ca45 100644 --- a/man/dims_helper.Rd +++ b/man/dims_helper.Rd @@ -4,14 +4,14 @@ \alias{dims_helper} \alias{dims_to_rowcol} \alias{rowcol_to_dims} -\alias{dims} +\alias{wb_dims} \title{Helper functions to work with \code{dims}} \usage{ dims_to_rowcol(x, as_integer = FALSE) rowcol_to_dims(row, col) -dims(...) +wb_dims(...) } \arguments{ \item{x}{a dimension object "A1" or "A1:A1"} @@ -22,7 +22,7 @@ dims(...) \item{col}{a numeric or character vector of cols} -\item{...}{dims arguments, row/col, rows/cols or objects that can be converted to data frame} +\item{...}{construct dims arguments, from rows/cols vectors or objects that can be coerced to data frame} } \value{ \itemize{ @@ -37,4 +37,8 @@ vector. Exported for user convenience. \examples{ dims_to_rowcol("A1:J10") rowcol_to_dims(1:10, 1:10) +# either vectors +wb_dims(rows = 1:10, cols = 1:10) +# or objects +wb_dims(mtcars) } diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 1a731b292..088581ed1 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -46,21 +46,30 @@ test_that("dims to col & row and back", { }) -test_that("dims() works", { +test_that("wb_dims() works", { # dim(mtcars) - expect_equal(dims(mtcars), "A1:K32") + expect_equal(wb_dims(mtcars), "A1:K32") + expect_equal(wb_dims(mtcars, col_names = TRUE, row_names = TRUE), "A1:L33") - expect_equal(dims(letters), "A1:A26") + expect_equal(wb_dims(letters), "A1:A26") - expect_equal(dims(t(letters)), "A1:Z1") + expect_equal(wb_dims(t(letters)), "A1:Z1") - expect_equal(dims(1), "A1") + expect_equal(wb_dims(1), "A1") - expect_equal(dims(rows = 1:10, cols = 5:7), "E1:G10") + expect_equal(wb_dims(rows = 1:10, cols = 5:7), "E1:G10") + expect_equal(wb_dims(cols = 1:10, rows = 5:7), "A5:J7") + expect_error( + wb_dims(cols = 1:10, col = 5:7), + "found only one cols/rows argument" + ) + + expect_equal(wb_dims(row = 5, col = 7), "G5") - expect_equal(dims(row = 5, col = 7), "G5") + expect_equal(wb_dims(1:10, LETTERS), "A1:Z10") + expect_equal(wb_dims(1:10, 1:26), "A1:Z10") }) From 357454763ce0d9093b6089fa022b01c1355bbde6 Mon Sep 17 00:00:00 2001 From: Jan Marvin Garbuszus Date: Sat, 15 Jul 2023 09:36:56 +0200 Subject: [PATCH 3/4] change default for wb_dims() --- R/utils.R | 23 +++++++++++++---------- tests/testthat/test-utils.R | 8 +++----- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/R/utils.R b/R/utils.R index e0c1ce359..adc8eacbe 100644 --- a/R/utils.R +++ b/R/utils.R @@ -276,17 +276,11 @@ wb_dims <- function(...) { col_names <- args$col_names row_names <- args$row_names - has_cnam <- is.null(col_names) - has_rnam <- is.null(row_names) - - if (has_cnam) col_names <- FALSE - if (has_rnam) row_names <- FALSE - - assert_class(col_names, "logical") - assert_class(row_names, "logical") + cnam_null <- is.null(col_names) + rnam_null <- is.null(row_names) # wb_dims(rows, cols) - if (length(args) == 2 && has_cnam && has_rnam) { + if (length(args) == 2 && cnam_null && rnam_null) { rows <- 1L cols <- 2L @@ -305,8 +299,17 @@ wb_dims <- function(...) { } else { + x <- args[[1]] + exp_name <- inherits(x, "data.frame") || inherits(x, "matrix") + + if (cnam_null) col_names <- exp_name + if (rnam_null) row_names <- FALSE + + assert_class(col_names, "logical") + assert_class(row_names, "logical") + # wb_dims(data.frame()) - x <- as.data.frame(args[[1]]) + x <- as.data.frame(x) rows <- seq_len(nrow(x) + col_names) cols <- seq_len(ncol(x) + row_names) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 088581ed1..b81b495a3 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -48,14 +48,12 @@ test_that("dims to col & row and back", { test_that("wb_dims() works", { - # dim(mtcars) - - expect_equal(wb_dims(mtcars), "A1:K32") - expect_equal(wb_dims(mtcars, col_names = TRUE, row_names = TRUE), "A1:L33") + expect_equal(wb_dims(mtcars), "A1:K33") + expect_equal(wb_dims(mtcars, col_names = FALSE, row_names = TRUE), "A1:L32") expect_equal(wb_dims(letters), "A1:A26") - expect_equal(wb_dims(t(letters)), "A1:Z1") + expect_equal(wb_dims(t(letters)), "A1:Z2") expect_equal(wb_dims(1), "A1") From ac76dd7370e225a66a2521bc8aeec976bdb3cd8b Mon Sep 17 00:00:00 2001 From: Jan Marvin Garbuszus Date: Sat, 15 Jul 2023 19:07:54 +0200 Subject: [PATCH 4/4] more tests, more cleanups, update NEWS --- NEWS.md | 9 ++++++++- R/utils.R | 22 +++++++++++++++++----- tests/testthat/test-utils.R | 3 +++ vignettes/openxlsx2.Rmd | 27 +++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 8ae83022c..45877de0a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -15,7 +15,7 @@ * remove deprecated arguments * `xy` argument - * arguments `col`, `row`, `cols`, `rows`. `start_col`, `start_row` and `gridExpand` were deprecated in favor of `dims`. Numeric vectors can be converted to `dims` using `rowcol_to_dims()` + * arguments `col`, `row`, `cols`, `rows`. `start_col`, `start_row` and `gridExpand` were deprecated in favor of `dims`. Row and column vectors can be converted to `dims` using `wb_dims()`. * deprecating function * `convertToExcelDate()` for `convert_to_excel_date()` @@ -23,6 +23,13 @@ * make `get_cell_refs()`, `get_date_origin()`, `guess_col_type()`, and `write_file()` internal functions * make classes `styles_mgr()`, `wbSheetData`, `wbWorksheet`, `wbChartsheet`, `wbComment`, `wbHyperlink` internal +## New features + +* `wb_dims()` was added as a more convenient replacement for `rowcol_to_dims()`. The new function can take either numeric (for rows or columns) or character (column) vectors, in addition it is able to create dimensions for R objects that are coerceable to data frame. This allows the following variants: +* `wb_dims(1:5, letters)` +* `wb_dims(1:5, 1:26)` +* `wb_dims(matrix(1, 5, 26))` with an added row for column names + ## Refactoring * Cleanup / revisit documentation and vignettes ([682](https://github.com/JanMarvin/openxlsx2/pull/682), @olivroy) diff --git a/R/utils.R b/R/utils.R index adc8eacbe..49b1f4829 100644 --- a/R/utils.R +++ b/R/utils.R @@ -279,8 +279,20 @@ wb_dims <- function(...) { cnam_null <- is.null(col_names) rnam_null <- is.null(row_names) + srow <- args$start_row + scol <- args$start_col + + scol_null <- is.null(scol) + srow_null <- is.null(srow) + + if (srow_null) srow <- 0 else srow <- srow - 1L + if (scol_null) scol <- 0 else scol <- col2int(scol) - 1L + + x <- args[[1]] + exp_name <- inherits(x, "data.frame") || inherits(x, "matrix") + # wb_dims(rows, cols) - if (length(args) == 2 && cnam_null && rnam_null) { + if (length(args) >= 2 && !exp_name) { rows <- 1L cols <- 2L @@ -295,13 +307,10 @@ wb_dims <- function(...) { } rows <- args[[rows]] - cols <- args[[cols]] + cols <- col2int(args[[cols]]) } else { - x <- args[[1]] - exp_name <- inherits(x, "data.frame") || inherits(x, "matrix") - if (cnam_null) col_names <- exp_name if (rnam_null) row_names <- FALSE @@ -315,6 +324,9 @@ wb_dims <- function(...) { } + rows <- rows + srow + cols <- cols + scol + if (length(rows) == 1 && length(cols) == 1) { # A1 dims <- rowcol_to_dim(rows, cols) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index b81b495a3..3f45a5f63 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -69,6 +69,9 @@ test_that("wb_dims() works", { expect_equal(wb_dims(1:10, LETTERS), "A1:Z10") expect_equal(wb_dims(1:10, 1:26), "A1:Z10") + expect_equal(wb_dims(1:2, 1:4, start_row = 2, start_col = "B"), "B2:E3") + expect_equal(wb_dims(mtcars, start_row = 2, start_col = "B"), "B2:L34") + }) test_that("create_char_dataframe", { diff --git a/vignettes/openxlsx2.Rmd b/vignettes/openxlsx2.Rmd index 2bf247541..56a08da5f 100644 --- a/vignettes/openxlsx2.Rmd +++ b/vignettes/openxlsx2.Rmd @@ -172,6 +172,33 @@ wb$save("mtcars.xlsx") try(wb$save("mtcars.xlsx", overwrite = FALSE)) ``` +## `dims` + +In `openxlsx2` functions that interact with worksheet cells are using `dims` as argument and require the users to provide these. `dims` are cells or cell ranges in A1 notation. The single argument `dims` hereby replaces `col`/`row`, `cols`/`rows` and `xy`. Since A1 notation is rather simple in the first few columns it might get confusing after the 26. Therefore we provide a wrapper to construct it: + +```{r} +# various options +wb_dims(4) + +wb_dims(row = 4, col = 4) + +wb_dims(row = 4:10, col = 5:9) + +wb_dims(row = 4:10, col = "A:D") + +wb_dims(mtcars) + +# in a wb chain +wb <- wb_workbook()$ + add_worksheet()$ + add_data(x = mtcars)$ + add_fill( + dims = wb_dims(mtcars, start_row = 5), + color = wb_color("yellow") + ) +``` + + ## A note on speed and memory usage The current state of `openxlsx2` is that it is reasonably fast. That is, it works well with reasonably large input data when reading or writing. It may not work well with data that tests the limits of the openxml specification. Things may slow down on the R side of things, and performance and usability will depend on the speed and size of the local operating system's CPU and memory.