diff --git a/DESCRIPTION b/DESCRIPTION index 26481d9..6e44772 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: a11ytables Title: Create Spreadsheet Publications Following Best Practice -Version: 0.2.1 +Version: 0.3 Authors@R: c( person(given = "Matt", family = "Dray", role = c("aut", "cre"), email = "mwdray@gmail.com"), person(given = "Tim", family = "Taylor", role = "ctb"), diff --git a/NAMESPACE b/NAMESPACE index dd4bb4b..1cd6fce 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,8 +4,6 @@ S3method(summary,a11ytable) S3method(tbl_sum,a11ytable) export(as_a11ytable) export(at_template_a11ytable) -export(at_template_df) -export(at_template_tibble) export(at_template_workflow) export(create_a11ytable) export(generate_workbook) diff --git a/NEWS.md b/NEWS.md index 9818a8d..7c57489 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,33 @@ +# a11ytables 0.3.0 + +## Breaking changes + +* The new `custom_rows` argument is the third-to-last argument to `create_a11ytable()` (it's been inserted before `sources` and `tables`) so that the argument order reflects the order of the pre-table rows that appear above tables in the spreadsheet output; this will be a problem for users of {a11ytables} before v0.3 who might have previously specified arguments _by place_ rather than _by name_ (I assume most will have specified arguments by name, so I expect this won't be much of a problem). + +## New features + +* Added the `custom_rows` argument to the `create_a11ytables()` function to supply arbitrary sentences to separate rows above a table in contents, cover and notes sheets (#74). +* Allowed custom rows to be hyperlinks if the user provides them in Markdown-style (#74). +* Added the datasets `demo_df` ('data.frame' class) and `demo_a11ytable` ('a11ytables' class) to help demo the new `custom_rows` argument (and do a better job of showcasing other features of the package), superseding `mtcars_df` and `mtcars_df2`. + +## Bugfixes + +* Adjusted Markdown-link detection in cases where the text portion contained parentheses (#119). +* Ensured the summary method printed table dimensions of cover list items (#79). + +## Documentation + +* Updated {roxygen2} function documentation given the introduction of `custom_rows`. +* Updated vignettes to include detail on how to use `custom_rows`. +* Updated accessibility checklist vignette to include 'sensible column width' item (#117). + +## Miscellaneous + +* Simplified the output of the summary method (#79). +* Updated tests to use `demo_*` datasets. +* Updated RStudio Addin given `custom_rows` and simplified to data.frame example only. +* Added internal `.vector_to_sentence()` function to help construct comma-separated lists from a vector. + # a11ytables 0.2.1 * Bugfix: corrected spelling error in installation instructions in README (#111). diff --git a/R/a11ytable.R b/R/a11ytable.R index d9d4a4d..d94cdde 100644 --- a/R/a11ytable.R +++ b/R/a11ytable.R @@ -1,9 +1,9 @@ #' Create An 'a11ytable' Object #' -#' Create a new a11ytable-class object, which is a dataframe that contains all -#' the information needed in your output spreadsheet. In turn, the object -#' created by this function can be used to populate an 'openxlsx' +#' Create a new a11ytable-class object, which is a special data.frame that +#' contains all the information needed in your output spreadsheet. In turn, the +#' object created by this function can be used to populate an 'openxlsx' #' Workbook-class object with the function \code{\link{generate_workbook}}. #' #' @param tab_titles Required character vector, one value per sheet. Each title @@ -18,8 +18,14 @@ #' @param sheet_titles Required character vector, one value per sheet. The main #' title for each sheet, which will appear in cell A1 (top-left corner). #' @param blank_cells Optional character vector, one value per sheet. A short -#' sentence to explain the reason for any blank cells in the sheet. Most -#' likely to be used with sheet type 'tables'. +#' sentence to explain the reason for any blank cells in the sheet. Supply +#' as \code{NA_character_} if empty. Most likely to be used with sheet type +#' 'tables'. +#' @param custom_rows Optional list of character vectors. One list element per +#' sheet, one character vector element per row of pre-table metadata. Supply +#' a list element as \code{NA_character_} if empty. To be used with sheet +#' type 'tables', but can also be used for sheet types 'contents' and +#' 'notes'. #' @param sources Optional character vector, one value per sheet. The origin of #' the data for a given sheet. Supply as \code{NA_character_} if empty. To #' be used with sheet type 'tables'. @@ -44,9 +50,10 @@ #' telephone number. You can use linebreaks (i.e. '\\n') to separate #' text into paragraphs. #' \item Sheet type 'contents': one row per sheet, two columns suggested at -#' least ('Tab title' and 'Worksheet title'). -#' \item Sheet type 'notes': one row per note, two columns suggested ('Note -#' number', 'Note text'), where notes are in the form '\[note 1\]'. +#' least (named 'Tab title' and 'Worksheet title'). +#' \item Sheet type 'notes': one row per note, two columns suggested (named +#' 'Note number', 'Note text'), where notes are in the form +#' '\[note 1\]'. #' \item Sheet type 'tables': a tidy, rectangular data.frame containing the #' data to be published. It's the user's responsibility to add notes in #' the form '\[note 1\]' to column headers, or in a special 'Notes' row. @@ -60,37 +67,104 @@ #' output spreadsheet. Note that whole cells will become hyperlinks; there is no #' support for selected words in a sentence to be rendered as a hyperlink. #' -#' Hyperlinks can be supplied in two locations: +#' Hyperlinks can be supplied in the character strings to three arguments: #' #' \itemize{ #' \item To the 'tables' argument for sheet type 'cover' only. It's #' recommended to supply the cover information as a list rather than a #' data.frame, which will allow you to make specific rows within a -#' section into hyperlinks. For example, in a 'Contact us' section you -#' might want a row containing some preamble (no hyperlink), a cell -#' containing a phone number (no hyperlink) and a cell containing an -#' email address (hyperlinked). -#' \item To the 'source' argument for data tables. +#' section (e.g. 'contact us') into hyperlinks. +#' \item To the 'custom_rows' argument for sheets of type 'contents, 'notes' +#' and 'tables'. +#' \item To the 'source' argument for sheets of type 'table' only. #' } #' #' @return An object with classes 'a11ytable', 'tbl' and 'data.frame'. #' #' @examples -#' # Create an a11ytable with in-built demo dataframe, mtcars_df2 -#' x <- create_a11ytable( -#' tab_titles = mtcars_df2$tab_title, -#' sheet_types = mtcars_df2$sheet_type, -#' sheet_titles = mtcars_df2$sheet_title, -#' blank_cells = mtcars_df2$blank_cells, -#' sources = mtcars_df2$source, -#' tables = mtcars_df2$table +#' # Prepare some demo tables of information +#' +#' set.seed(1066) +#' +#' cover_list <- list( +#' "Section 1" = c("First row of Section 1.", "Second row of Section 1."), +#' "Section 2" = "The only row of Section 2.", +#' "Section 3" = c( +#' "[Website](https://co-analysis.github.io/a11ytables/)", +#' "[Email address](mailto:fake.address@a11ytables.com)" +#' ) +#' ) +#' +#' contents_df <- data.frame( +#' "Sheet name" = c("Notes", "Table_1", "Table_2"), +#' "Sheet title" = c( +#' "Notes used in this workbook", +#' "First Example Sheet", +#' "Second Example Sheet" +#' ), +#' check.names = FALSE #' ) #' +#' notes_df <- data.frame( +#' "Note number" = paste0("[note ", 1:3, "]"), +#' "Note text" = c("First note.", "Second note.", "Third note."), +#' check.names = FALSE +#' ) +#' +#' table_1_df <- data.frame( +#' Category = LETTERS[1:10], +#' "Numeric [note 1]" = 1:10, +#' "Numeric suppressed" = c(1:4, "[c]", 6:9, "[x]"), +#' "Numeric thousands" = abs(round(rnorm(10), 4) * 1e5), +#' "Numeric decimal" = abs(round(rnorm(10), 5)), +#' "This column has a very long name that means that the column width needs to be widened" = 1:10, +#' Notes = c("[note 1]", rep(NA_character_, 4), "[note 2]", rep(NA_character_, 4)), +#' check.names = FALSE +#' ) +#' +#' table_2_df <- data.frame(Category = LETTERS[1:10], Numeric = 1:10) +#' +#' # Create 'a11ytables' object +#' +#' x <- +#' a11ytables::create_a11ytable( +#' tab_titles = c("Cover", "Contents", "Notes", "Table_1", "Table_2"), +#' sheet_types = c("cover", "contents", "notes", "tables", "tables"), +#' sheet_titles = c( +#' "The 'a11ytables' Demo Workbook", +#' "Table of contents", +#' "Notes", +#' "Table 1: First Example Sheet", +#' "Table 2: Second Example Sheet" +#' ), +#' blank_cells = c( +#' rep(NA_character_, 3), +#' "Blank cells indicate that there's no note in that row.", +#' NA_character_ +#' ), +#' custom_rows = list( +#' NA_character_, +#' NA_character_, +#' "A custom row.", +#' c( +#' "First custom row [with a hyperlink.](https://co-analysis.github.io/a11ytables/)", +#' "Second custom row." +#' ), +#' "A custom row." +#' ), +#' sources = c( +#' rep(NA_character_, 3), +#' "[The Source Material, 2024.](https://co-analysis.github.io/a11ytables/)", +#' "The Source Material, 2024." +#' ), +#' tables = list(cover_list, contents_df, notes_df, table_1_df, table_2_df) +#' ) +#' #' # Test that 'a11ytable' is one of the object's classes #' is_a11ytable(x) #' -#' # You can also use the RStudio Addin installed with the package to insert a -#' # an example skeleton containing this function. +#' # Look at the structure of the object +#' str(x, max.level = 2) #' #' @export create_a11ytable <- function( @@ -99,6 +173,7 @@ create_a11ytable <- function( sheet_titles, blank_cells = NA_character_, sources = NA_character_, + custom_rows = list(NA_character_), tables ) { @@ -111,6 +186,7 @@ create_a11ytable <- function( stringsAsFactors = FALSE # because default is TRUE prior to R v4 ) + x[["custom_rows"]] <- custom_rows x[["table"]] <- tables as_a11ytable(x) @@ -128,13 +204,7 @@ create_a11ytable <- function( #' a11ytable, otherwise \code{FALSE}. #' #' @examples -#' # Create an a11ytable with in-built demo dataframe, mtcars_df2. We can use -#' # 'as_a11ytable' rather than 'create_a11ytable' because the data is already -#' # in the right format. -#' x <- as_a11ytable(mtcars_df2) -#' -#' # Test the object's class -#' is_a11ytable(x) +#' is_a11ytable(demo_a11ytable) #' #' @export as_a11ytable <- function(x) { @@ -170,42 +240,61 @@ is_a11ytable <- function(x) { #' Summarise An 'a11ytable' Object #' -#' A concise result summary of an a11ytable-class object to see information about -#' the sheet content. +#' A concise result summary of an a11ytable-class object to see information +#' about the sheet content. Shows a numbered list of sheets with each tab title, +#' sheet type and table dimensions. #' #' @param object An a11ytable-class object for which to get a summary. #' @param ... Other arguments to pass. #' #' @examples -#' # Create an a11ytable with in-built demo dataframe, mtcars_df2. We can use -#' # 'as_a11ytable' rather than 'create_a11ytable' because the data is already -#' # in the right format. -#' x <- as_a11ytable(mtcars_df2) +#' # Print a concise summary of the a11ytable-class object +#' summary(demo_a11ytable) #' -#' # Print summary of a11ytable-class object -#' summary(x) +#' # Alternatively, look at the structure +#' str(demo_a11ytable, max.level = 2) #' #' @export summary.a11ytable <- function(object, ...) { - x_dims <- lapply( - lapply(object[["table"]], dim), - function(x) paste(x, collapse = " x ") - ) + tables <- object[["table"]] + + table_dims <- vector("list", length = length(tables)) - tab_title <- paste0("\n", paste(" -", object[["tab_title"]], collapse = "\n")) - sh_type <- paste0("\n", paste(" -", object[["sheet_type"]], collapse = "\n")) - sh_title <- paste0("\n", paste(" -", object[["sheet_title"]], collapse = "\n")) - tbl_dims <- paste0("\n", paste(" -", unlist(x_dims), collapse = "\n")) - - cat( - "# An a11ytable with", nrow(object), "sheets\n", - "* Tab titles:", tab_title, "\n", - "* Sheet types:", sh_type, "\n", - "* Sheet titles:", sh_title, "\n", - "* Table sizes:", tbl_dims, "\n" + for (i in seq_along(tables)) { + + if (inherits(tables[[i]], "list")) { + + list_length <- length(tables[[i]]) + list_lengths <- lengths(tables[[i]]) + + table_dims[[i]] <- paste0( + "list of length ", list_length, + " (element lengths ", .vector_to_sentence(list_lengths), ")" + ) + + } + + if (is.data.frame(tables[[i]])) { + table_dims[[i]] <- + paste(paste(dim(tables[[i]]), collapse = " x "), "dataframe") + } + + } + + summary_string <- paste0( + "\n", + paste0( + paste0(" ", seq_along(tables), ") Tab '"), + object[["tab_title"]], + "' (sheet type '", object[["sheet_type"]], "') contains a ", + unlist(table_dims), + collapse = "\n" + ) ) + cat("# An a11ytable with", nrow(object), "sheets:", summary_string) + invisible(object) } @@ -215,7 +304,7 @@ NULL #' Provide A Succinct Summary Of An 'a11ytable' Object #' -#' A brief textual description of an a11ytable-class object. +#' A brief text description of an a11ytable-class object. #' #' @param x An a11ytable-class object to summarise. #' @param ... Other arguments to pass. @@ -223,18 +312,11 @@ NULL #' @return Named character vector. #' #' @examples -#' \dontrun{ -#' # Create an a11ytable with in-built demo dataframe, mtcars_df2. We can use -#' # 'as_a11ytable' rather than 'create_a11ytable' because the data is already -#' # in the right format. -#' x <- as_a11ytable(mtcars_df2) -#' -#' # Print description only -#' tbl_sum(x) -#' #' # Print with description -#' print(x) -#' } +#' print(demo_a11ytable) +#' +#' # Print description only (package 'tibble' must be installed) +#' tibble::tbl_sum(demo_a11ytable) #' #' @export tbl_sum.a11ytable <- function(x, ...) { diff --git a/R/addin.R b/R/addin.R index 0c5488f..2b05476 100644 --- a/R/addin.R +++ b/R/addin.R @@ -1,49 +1,27 @@ -#' Insert 'a11ytable' Template +#' Insert Demo 'create_a11ytable' Template #' #' Insert at the cursor a template for \code{\link{create_a11ytable}} from the -#' 'a11ytable' package, pre-filled with example information. +#' 'a11ytable' package, pre-filled with demo data. #' #' @export at_template_a11ytable <- function() { rstudioapi::insertText(string_create_a11ytable()) } -#' Insert Table Templates Using 'tibble' +#' Insert Full Demo 'a11ytables' Template Workflow #' -#' Insert at the cursor templates for cover, contents and notes tables, -#' pre-filled with example information. Requires the 'tibble' package to be -#' installed. -#' -#' @export -at_template_tibble <- function() { - rstudioapi::insertText(string_tables_tibble()) -} - -#' Insert Table Templates Using 'data.frame' -#' -#' Insert at the cursor templates for cover, contents and notes -#' tables, pre-filled with example information. Uses \code{\link{data.frame}}, -#' so isn't dependent on external packages. -#' -#' @export -at_template_df <- function() { - rstudioapi::insertText(string_tables_df()) -} - -#' Insert Full 'a11ytables' Template Workflow -#' -#' Insert at the cursor templates for cover, contents and notes -#' tables, and \code{\link{create_a11ytable}}, which are all pre-filled with -#' example information. +#' Insert at the cursor (a) demo templates for cover, contents and notes +#' tables, and (b) a call to \code{\link{create_a11ytable}} pre-filled with +#' demo data. #' #' @export at_template_workflow <- function() { rstudioapi::insertText( paste0( - "# Prepare tables", + "# Prepare tables of information", "\n\n", - string_tables_tibble(), + string_tables(), "\n\n", "# Create new a11ytable", "\n\n", @@ -57,8 +35,92 @@ at_template_workflow <- function() { "\n\n", "openxlsx::openXL(my_wb) # open temp copy", "\n\n", - 'openxlsx::saveWorkbook(my_wb, "~/Desktop/example.xlsx")' + 'openxlsx::saveWorkbook(my_wb, "example.xlsx") # change save location' ) ) } + +#' A String Containing Code to Prepare Tables for an 'a11ytables' Object +#' @noRd +string_tables <- function() { + + 'cover_list <- list( + "Section 1" = c("First row of Section 1.", "Second row of Section 1."), + "Section 2" = "The only row of Section 2.", + "Section 3" = c( + "[Website](https://co-analysis.github.io/a11ytables/)", + "[Email address](mailto:fake.address@a11ytables.com)" + ) +) + +contents_df <- data.frame( + "Sheet name" = c("Notes", "Table 1", "Table 2"), + "Sheet title" = c( + "Notes used in this workbook", + "First Example Sheet", + "Second Example Sheet" + ), + check.names = FALSE +) + +notes_df <- data.frame( + "Note number" = paste("[note ", 1:2, "]"), + "Note text" = c("First note.", "Second note."), + check.names = FALSE +) + +table_1_df <- data.frame( + Category = LETTERS[1:10], + Numeric = 1:10, + "Numeric suppressed" = c(1:4, "[c]", 6:9, "[x]"), + "Numeric thousands" = abs(round(rnorm(10), 4) * 1e5), + "Numeric decimal" = abs(round(rnorm(10), 5)), + "A column with a long name" = 1:10, + Notes = c("[note 1]", rep(NA_character_, 4), "[note 2]", rep(NA_character_, 4)), + check.names = FALSE +) + +table_2_df <- data.frame(Category = LETTERS[1:10], Numeric = 1:10)' + +} + +#' A String Containing Code to Generate an 'a11ytables' Object +#' @noRd +string_create_a11ytable <- function() { + + 'my_a11ytable <- + a11ytables::create_a11ytable( + tab_titles = c("Cover", "Contents", "Notes", "Table_1", "Table_2"), + sheet_types = c("cover", "contents", "notes", "tables", "tables"), + sheet_titles = c( + "The \'a11ytables\' Demo Workbook", + "Table of contents", + "Notes", + "Table 1: First Example Sheet", + "Table 2: Second Example Sheet" + ), + blank_cells = c( + rep(NA_character_, 3), + "Blank cells indicate that there\'s no note in that row.", + NA_character_ + ), + custom_rows = list( + NA_character_, + "A custom row in the Contents sheet.", + NA_character_, + c( + "First custom row for Table 1.", + "A second custom row [with a hyperlink.](https://co-analysis.github.io/a11ytables/)" + ), + "A custom row for Table 2" + ), + sources = c( + rep(NA_character_, 3), + "[The Source Material, 2024](https://co-analysis.github.io/a11ytables/)", + "The Source Material, 2024" + ), + tables = list(cover_list, contents_df, notes_df, table_1_df, table_2_df) + )' + +} diff --git a/R/data.R b/R/data.R index 57e111b..eefc5e7 100644 --- a/R/data.R +++ b/R/data.R @@ -1,15 +1,70 @@ -#' Test Data: A Modified 'mtcars' Dataframe +#' A Demo 'data.frame' Object +#' +#' A pre-created data.frame ready to be converted to an a11ytables-class object +#' with \code{\link{as_a11ytable}} and then an 'openxlsx' Workbook-class object +#' with \code{\link{generate_workbook}}. +#' +#' @format A data.frame with 6 rows and 7 columns: +#' \describe{ +#' \item{tab_title}{Character. Text to appear on each sheet's tab.} +#' \item{sheet_type}{Character. The content type for each sheet: 'cover', 'contents', 'notes', or 'tables'.} +#' \item{sheet_title}{Character. The title that will appear in cell A1 (top-left) of each sheet.} +#' \item{blank_cells}{Character. An explanation for any blank cells in the table.} +#' \item{custom_rows}{List-column of character vectors. Additional arbitrary pre-table information provided by the user.} +#' \item{source}{Character. The origin of the data, if relevant.} +#' \item{table}{List-column of data.frames (apart from the cover, which is a list) containing the statistical tables.} +#' } +"demo_df" + +#' A Demo 'a11ytables' Object +#' +#' A pre-created 'a11ytables' object ready to be converted to an 'openxlsx' +#' Workbook-class object with \code{\link{generate_workbook}}. +#' +#' @format A data.frame with 6 rows and 7 columns: +#' \describe{ +#' \item{tab_title}{Character. Text to appear on each sheet's tab.} +#' \item{sheet_type}{Character. The content type for each sheet: 'cover', 'contents', 'notes', or 'tables'.} +#' \item{sheet_title}{Character. The title that will appear in cell A1 (top-left) of each sheet.} +#' \item{blank_cells}{Character. An explanation for any blank cells in the table.} +#' \item{custom_rows}{List-column of character vectors. Additional arbitrary pre-table information provided by the user.} +#' \item{source}{Character. The origin of the data, if relevant.} +#' \item{table}{List-column of data.frames (apart from the cover, which is a list) containing the statistical tables.} +#' } +"demo_a11ytable" + +#' A Demo 'Workbook' Object +#' +#' A pre-created 'openxlsx' Workbook'-class object generated from an +#' a11ytables-class object with \code{\link{generate_workbook}}. +#' +#' @format An 'openxlsx' Workbook-class object with 5 sheets. +"demo_workbook" + +#' Test Data: A Modified 'mtcars' Dataframe (Version 1) +#' +#' @description +#' Superseded. mtcars_df and \code{\link{mtcars_df2}} have been superseded in +#' favour of \code{\link{demo_df}}. #' #' A modified version of the mtcars dataset prepared into a data.frame structure #' ready for coercion to an a11ytables-class object with #' \code{\link{as_a11ytable}}. Uses a dataframe as input to the cover table; #' \code{\link{mtcars_df}} uses a list as input to the cover table. #' -#' @format A data frame with 5 rows and 7 columns: +#' @details +#' Uses a data.frame as input to the cover table, whereas +#' \code{\link{mtcars_df2}} uses a list as input to the cover table +#' (implemented in version 0.2). +#' +#' Note that this dataset is superseded by \code{\link{demo_df}} but is +#' retained for backwards-compatibility with package versions prior to 0.3. +#' +#' @format A data frame with 5 rows and 6 columns: #' \describe{ -#' \item{tab_title}{Character. Text to appear on the sheet's tab.} +#' \item{tab_title}{Character. Text to appear on each sheet's tab.} #' \item{sheet_type}{Character. The content type for each sheet: 'cover', 'contents', 'notes', or 'tables'.} -#' \item{sheet_title}{Character. The title that will appear in the top-left of each sheet.} +#' \item{sheet_title}{Character. The title that will appear in cell A1 (top-left) of each sheet.} #' \item{blank_cells}{Character. An explanation for any blank cells in the table.} #' \item{source}{Character. The origin of the data, if relevant.} #' \item{table}{List-column of data.frames containing the statistical tables.} @@ -18,18 +73,28 @@ #' @source \code{\link[datasets:mtcars]{mtcars}} "mtcars_df" -#' Test Data: A Modified 'mtcars' Dataframe +#' Test Data: A Modified 'mtcars' Dataframe (Version 2) +#' +#' @description +#' Superseded. \code{\link{mtcars_df}} and mtcars_df2 have been superseded in +#' favour of \code{\link{demo_df}}. #' #' A modified version of the mtcars dataset prepared into a data.frame structure #' ready for coercion to an a11ytables-class object with -#' \code{\link{as_a11ytable}}. Uses a list as input to the cover table; -#' \code{\link{mtcars_df}} uses a dataframe as input to the cover table. +#' \code{\link{as_a11ytable}}. +#' +#' @details +#' Uses a list as input to the cover table (implemented in version 0.2), whereas +#' \code{\link{mtcars_df}} uses a data.frame as input to the cover table. #' -#' @format A data frame with 5 rows and 7 columns: +#' Note that this dataset is superseded by \code{\link{demo_df}} but is +#' retained for backwards-compatibility with package versions starting 0.2. +#' +#' @format A data frame with 5 rows and 6 columns: #' \describe{ -#' \item{tab_title}{Character. Text to appear on the sheet's tab.} +#' \item{tab_title}{Character. Text to appear on each sheet's tab.} #' \item{sheet_type}{Character. The content type for each sheet: 'cover', 'contents', 'notes', or 'tables'.} -#' \item{sheet_title}{Character. The title that will appear in the top-left of each sheet.} +#' \item{sheet_title}{Character. The title that will appear in cell A1 (top-left) of each sheet.} #' \item{blank_cells}{Character. An explanation for any blank cells in the table.} #' \item{source}{Character. The origin of the data, if relevant.} #' \item{table}{List-column of data.frames (apart from the cover, which is a list) containing the statistical tables.} @@ -37,3 +102,5 @@ #' #' @source \code{\link[datasets:mtcars]{mtcars}} "mtcars_df2" + + diff --git a/R/utils-a11ytable.R b/R/utils-a11ytable.R index 7ab6f18..a4308d6 100644 --- a/R/utils-a11ytable.R +++ b/R/utils-a11ytable.R @@ -51,7 +51,13 @@ .validate_a11ytable <- function(x) { names_req <- c( - "tab_title", "sheet_type", "sheet_title", "blank_cells", "source", "table" + "tab_title", + "sheet_type", + "sheet_title", + "blank_cells", + "source", + "custom_rows", + "table" ) names_count <- length(names_req) names_in <- names(x) @@ -85,12 +91,27 @@ ) } - # Class must be character for all columns except 'table' - if (!all(unlist(lapply(subset(x, select = -table), is.character)))) { - stop("All columns except 'table' must be character class.", call. = FALSE) + # 'custom_row' column class must be listcol + if (!inherits(x[["custom_rows"]], "list")) { + stop( + "Column 'table' must be a listcol of character vectors", + call. = FALSE + ) + } + + # Class must be character for all columns except 'table' and 'custom_rows' + + char_cols <- x[, !names(x) %in% c("table", "custom_rows")] + are_char_cols <- unlist(lapply(char_cols, is.character)) + + if (!all(are_char_cols)) { + stop( + "All columns except 'table' and 'custom_rows' must be character class.", + call. = FALSE + ) } - # Content of listcol column must be single data.frame objects (or cover list) + # Content of 'table' listcol must be single data.frame objects (or cover list) if (!all(unlist(lapply(x[["table"]], is.list)))) { stop( "List-column 'table' must contain data.frame objects only. ", @@ -99,6 +120,14 @@ ) } + # Content of 'custom_row' listcol must be character + if (!all(unlist(lapply(x[["custom_rows"]], is.character)))) { + stop( + "List-column 'custom_rows' must contain character vectors only. ", + call. = FALSE + ) + } + # There must be cover and contents sheets if (sum(x[["sheet_type"]] %in% c("cover", "contents")) < 2) { stop( @@ -147,7 +176,6 @@ stop("Each 'tab_title' must be unique (case-insensitive).", call. = FALSE) } - } #' Warn if an 'a11ytable' Has a Non-critical Problem diff --git a/R/utils-addin.R b/R/utils-addin.R deleted file mode 100644 index 05207e1..0000000 --- a/R/utils-addin.R +++ /dev/null @@ -1,107 +0,0 @@ -string_create_a11ytable <- function() { - - 'my_a11ytable <- - a11ytables::create_a11ytable( - tab_titles = c( - "Cover", - "Contents", - "Notes", - "Table_1" - ), - sheet_types = c( - "cover", - "contents", - "notes", - "tables" - ), - sheet_titles = c( - "Cover title (example)", - "Contents", - "Notes", - "Example sheet title" - ), - blank_cells = c( - NA_character_, - NA_character_, - NA_character_, - "Blank cells mean that a row does not have a note." - ), - sources = c( - NA_character_, - NA_character_, - NA_character_, - "Example source." - ), - tables = list( - cover_df, - contents_df, - notes_df, - table_df - ) - )' - -} - -string_tables_tibble <- function() { - - 'cover_df <- tibble::tribble( - ~subsection_title, ~subsection_content, - "Purpose", "Example results for something.", - "Workbook properties", "Some placeholder information.", - "Contact", "Placeholder email" - ) - - contents_df <- tibble::tribble( - ~"Sheet name", ~"Sheet title", - "Notes", "Notes", - "Table_1", "Example sheet title" - ) - - notes_df <- tibble::tribble( - ~"Note number", ~"Note text", - "[note 1]", "Placeholder note.", - "[note 2]", "Placeholder note." - ) - - table_df <- mtcars - table_df[["car [note 1]"]] <- row.names(mtcars) - row.names(table_df) <- NULL - table_df <- table_df[1:5, c("car [note 1]", "mpg", "cyl")] - table_df["Notes"] <- c("[note 2]", rep(NA_character_, 4))' - -} - -string_tables_df <- function() { - 'cover_df <- data.frame( - subsection_title = c( - "Purpose", - "Workbook properties", - "Contact" - ), - subsection_content = c( - "Example results for something.", - "Some placeholder information.", - "Placeholder email" - ), - check.names = FALSE - ) - - contents_df <- data.frame( - "Sheet name" = c("Notes", "Table_1"), - "Sheet title" = c("Notes", "Example sheet title"), - check.names = FALSE - ) - - notes_df <- data.frame( - "Note number" = c("[note 1]", "[note 2]"), - "Note text" = c("Placeholder note.", "Placeholder note."), - check.names = FALSE - ) - - table_df <- mtcars - table_df[["car [note 1]"]] <- row.names(mtcars) - row.names(table_df) <- NULL - table_df <- table_df[1:5, c("car [note 1]", "mpg", "cyl")] - table_df["Notes"] <- c("[note 2]", rep(NA_character_, 4))' - -} diff --git a/R/utils-workbook-style.R b/R/utils-workbook-style.R index 728c8aa..8bd1a0c 100644 --- a/R/utils-workbook-style.R +++ b/R/utils-workbook-style.R @@ -1,4 +1,5 @@ - +#' Set Up a List of Common Styles +#' @noRd .style_create <- function() { list( @@ -12,6 +13,9 @@ } +#' Apply Styles to the Whole Workbook +#' @param wb An 'openxlsx' Workbook object. +#' @noRd .style_workbook <- function(wb) { openxlsx::modifyBaseFont( @@ -24,6 +28,11 @@ } +#' Apply Styles to a Sheet Title +#' @param wb An 'openxlsx' Workbook object. +#' @param tab_title Character. The tab in `wb` where the style should be set. +#' @param style_ref List. The style-reference object made with [.style_create]. +#' @noRd .style_sheet_title <- function(wb, tab_title, style_ref) { # Sheet titles are BOLD and 16PT @@ -31,18 +40,18 @@ openxlsx::addStyle( wb = wb, sheet = tab_title, - rows = 1, # will always be cell A1 + rows = 1, cols = 1, - style = style_ref$bold, + style = style_ref[["bold"]], stack = TRUE ) openxlsx::addStyle( wb = wb, sheet = tab_title, - rows = 1, # will always be cell A1 + rows = 1, cols = 1, - style = style_ref$pt16, + style = style_ref[["pt16"]], stack = TRUE ) @@ -50,10 +59,111 @@ } +#' Apply Styles to a Table +#' @param wb An 'openxlsx' Workbook object. +#' @param table_name Character. The table to which styles should be applied. +#' @param style_ref List. The style-reference object made with [.style_create]. +#' @noRd +.style_table <- function(wb, content, table_name, style_ref) { + + content_row <- content[content[["table_name"]] == table_name, ] + table <- content_row[, "table"][[1]] + tab_title <- content_row[, "tab_title"][[1]] + sheet_type <- content_row[, "sheet_type"][[1]] + + start_row <- .get_start_row_table( + content, + tab_title, + .has_notes(content, tab_title), + .has_blanks_message(content, tab_title), + .has_custom_rows(content, tab_title), + .has_source(content, tab_title) + ) + + table_height <- nrow(table) + table_width <- ncol(table) + + cellwidth_default <- 16 + cellwidth_wider <- 32 + nchar_break <- 50 + + # Some columns may contain numbers but have suppression text in them, e.g. + # '[c]', which makes the column character class. Find the likely numeric cols. + cols_numeric <- suppressWarnings(lapply(table, as.numeric)) # coerce columns to numeric + cols_numeric <- lapply(cols_numeric, function(x) any(!is.na(x))) # at least one number after coercion? + likely_num_cols <- names(Filter(isTRUE, cols_numeric)) # return names of columns that are most likely numeric + num_cols_index <- which(names(table) %in% likely_num_cols) # get the index of columns that are likely numeric, so styles can be applied + + # Columns that should be wider than default + wide_cells <- names(Filter(function(x) max(nchar(x)) > nchar_break, table)) + wide_cells_index <- which(names(table) %in% wide_cells) + wide_headers_index <- which(nchar(names(table)) > nchar_break) + wide_cols_index <- c(wide_cells_index, wide_headers_index) + + # Table data columns are SET-WIDTH (depending on character length), + # RIGHT-ALIGNED (if numeric) and WRAPPED + + openxlsx::setColWidths( + wb = wb, + sheet = tab_title, + cols = seq(table_width), + widths = cellwidth_default # set all columns to default width first + ) + + if (length(wide_cols_index[!is.na(wide_cols_index)])) { # only run if neded + openxlsx::setColWidths( + wb = wb, + sheet = tab_title, + cols = wide_cols_index, + widths = cellwidth_wider # apply larger width to certain columns + ) + } + + openxlsx::addStyle( + wb = wb, + sheet = tab_title, + rows = seq(start_row, start_row + table_height), + cols = seq(table_width), + gridExpand = TRUE, + style = style_ref[["wrap"]], + stack = TRUE + ) + + openxlsx::addStyle( + wb = wb, + sheet = tab_title, + rows = seq(start_row, start_row + table_height), + cols = num_cols_index, + gridExpand = TRUE, + style = style_ref[["ralign"]], + stack = TRUE + ) + + # Table headers are also BOLD + + openxlsx::addStyle( + wb = wb, + sheet = tab_title, + rows = start_row, + cols = seq(table_width), + style = style_ref[["bold"]], + stack = TRUE + ) + + return(wb) + +} + +#' Apply Styles to the Cover Sheet +#' @param wb An 'openxlsx' Workbook object. +#' @param tab_title Character. The tab in `wb` where the style should be set. +#' @param style_ref List. The style-reference object made with [.style_create]. +#' @noRd .style_cover <- function(wb, content, style_ref) { - tab_name <- content[content$sheet_type == "cover", "tab_title"][[1]] - table <- content[content$sheet_type == "cover", "table"][[1]] + content_row <- content[content[["sheet_type"]] == "cover", ] + tab_name <- content_row[, "tab_title"][[1]] + table <- content_row[, "table"][[1]] # The cover column is SET-WIDTH @@ -64,49 +174,53 @@ widths = 72 ) - if (is.data.frame(table)) { + # The cover content can be provided as a list or data.frame + cover_is_list <- inherits(table, "list") + cover_is_df <- is.data.frame(table) - # The cover column is WRAPPED + if (cover_is_list) { - table_height <- nrow(table) + table_vec <- unlist(c(rbind(names(table), table))) + + # The cover column is SET-WIDTH and WRAPPED + + table_height <- length(table_vec) openxlsx::addStyle( wb = wb, sheet = tab_name, - rows = seq(table_height * 2 + 1), # include sheet title + rows = seq(table_height + 1), cols = 1, - style = style_ref$wrap, + style = style_ref[["wrap"]], stack = TRUE ) - # Also identify rows containing headers - subheader_rows <- seq(2, table_height * 2, 2) + # Also identify rows containing section headers + subheader_rows <- which(table_vec %in% names(table)) + 1 } - if (is.list(table) & !is.data.frame(table)) { - - table_vec <- unlist(c(rbind(names(table), table))) + if (cover_is_df) { - # The cover column is SET-WIDTH and WRAPPED + # The cover column is WRAPPED - table_height <- length(table_vec) + table_height <- nrow(table) openxlsx::addStyle( wb = wb, sheet = tab_name, - rows = seq(table_height + 1), # include sheet title + rows = seq(table_height * 2 + 1), cols = 1, - style = style_ref$wrap, + style = style_ref[["wrap"]], stack = TRUE ) - # Also identify rows containing headers - subheader_rows <- which(table_vec %in% names(table)) + 1 + # Also identify rows containing section headers + subheader_rows <- seq(2, table_height * 2, 2) } - # Subheader rows also have LARGER ROW HEIGHT, are BOLD and 14PT + # Section header rows also have LARGER ROW HEIGHT, are BOLD and 14PT openxlsx::setRowHeights( wb = wb, @@ -120,7 +234,7 @@ sheet = tab_name, rows = subheader_rows, cols = 1, - style = style_ref$bold, + style = style_ref[["bold"]], stack = TRUE ) @@ -129,7 +243,7 @@ sheet = tab_name, rows = subheader_rows, cols = 1, - style = style_ref$pt14, + style = style_ref[["pt14"]], stack = TRUE ) @@ -137,196 +251,121 @@ } -.style_table <- function(wb, content, table_name, style_ref) { - - table <- content[content$table_name == table_name, "table"][[1]] - tab_title <- content[content$table_name == table_name, "tab_title"][[1]] - sheet_type <- content[content$table_name == table_name, "sheet_type"][[1]] +#' Apply Styles to the Contents Sheet +#' @param wb An 'openxlsx' Workbook object. +#' @param tab_title Character. The tab in `wb` where the style should be set. +#' @param style_ref List. The style-reference object made with [.style_create]. +#' @noRd +.style_contents <- function(wb, content, style_ref) { - has_notes <- .has_notes(content, tab_title) - has_blanks_message <- .has_blanks_message(content, tab_title) - has_source <- .has_source(content, tab_title) - start_row <- .get_start_row_table(has_notes, has_blanks_message, has_source) + tab_title <- content[content[["sheet_type"]] == "contents", "tab_title"][[1]] + table <- content[content[["sheet_type"]] == "contents", "table"][[1]] table_height <- nrow(table) - table_width <- ncol(table) - - cellwidth_default <- 16 - cellwidth_wider <- 32 - nchar_break <- 50 - - # Some columns may contain numbers but have suppression text in them, e.g. - # '[c]', which makes the column character class. Find the likely numeric cols. - suppressWarnings( # coercion to numeric may trigger a warning - likely_num_cols <- - names( # return names of columns that are most likely numeric - Filter( - isTRUE, # isolate the columns that are likely numeric - lapply( - lapply(table, as.numeric), # coerce cols to numeric - function(x) any(!is.na(x)) # at least one number after coercion? - ) - ) - ) - ) - - # Get the index of columns that are likely numeric, so styles can be applied - num_cols_index <- which(names(table) %in% likely_num_cols) - - if (sheet_type %in% c("cover", "contents", "notes")) { - table_header_row <- 3 - } - - if (sheet_type == "tables") { - table_header_row <- start_row - } - - # Columns that should be wider than default - wide_cells <- names(Filter(function(x) max(nchar(x)) > nchar_break, table)) - wide_cells_index <- which(names(table) %in% wide_cells) - wide_headers_index <- which(nchar(names(table)) > 50) - wide_cols_index <- c(wide_cells_index, wide_headers_index) - - # Table data columns are SET-WIDTH (depending on character length), - # RIGHT-ALIGNED (if numeric) and WRAPPED - - openxlsx::setColWidths( - wb = wb, - sheet = tab_title, - cols = seq(table_width), - widths = cellwidth_default # set all columns to default width first - ) - - if (length(wide_cols_index[!is.na(wide_cols_index)])) { # only run if neded - openxlsx::setColWidths( - wb = wb, - sheet = tab_title, - cols = wide_cols_index, - widths = cellwidth_wider # apply larger width to certain columns - ) - } - - openxlsx::addStyle( - wb = wb, - sheet = tab_title, - rows = seq(table_header_row, table_header_row + table_height), - cols = seq(table_width), - gridExpand = TRUE, - style = style_ref$wrap, - stack = TRUE - ) - - openxlsx::addStyle( - wb = wb, - sheet = tab_title, - rows = seq(table_header_row, table_header_row + table_height), - cols = num_cols_index, # right-align numeric columns only - gridExpand = TRUE, - style = style_ref$ralign, - stack = TRUE - ) - - # Table headers are also BOLD + table_width <- ncol(table) - openxlsx::addStyle( - wb = wb, - sheet = tab_title, - rows = table_header_row, - cols = seq(table_width), - style = style_ref$bold, - stack = TRUE + start_row <- .get_start_row_table( + content, + tab_title, + .has_notes(content, tab_title), + .has_blanks_message(content, tab_title), + .has_custom_rows(content, tab_title), + .has_source(content, tab_title) ) - return(wb) - -} - -.style_contents <- function(wb, content, style_ref) { - - tab_name <- content[content$sheet_type == "contents", "tab_title"][[1]] - table <- content[content$sheet_type == "contents", "table"][[1]] - table_height <- nrow(table) - table_width <- ncol(table) - # Contents columns are SET-WIDTH, WRAPPED and LEFT ALIGNED openxlsx::setColWidths( wb = wb, - sheet = tab_name, + sheet = tab_title, cols = 1, widths = 16 ) openxlsx::setColWidths( wb = wb, - sheet = tab_name, + sheet = tab_title, cols = 2, widths = 56 ) openxlsx::addStyle( wb = wb, - sheet = tab_name, - rows = seq(table_height + 1) + 2, + sheet = tab_title, + rows = seq(start_row, table_height + start_row), cols = seq(table_width), gridExpand = TRUE, - style = style_ref$wrap, + style = style_ref[["wrap"]], stack = TRUE ) openxlsx::addStyle( wb = wb, - sheet = tab_name, - rows = seq(table_height + 1) + 2, + sheet = tab_title, + rows = seq(start_row, table_height + start_row), cols = seq(table_width), gridExpand = TRUE, - style = style_ref$lalign, + style = style_ref[["lalign"]], stack = TRUE ) } - +#' Apply Styles to the Notes Sheet +#' @param wb An 'openxlsx' Workbook object. +#' @param tab_title Character. The tab in `wb` where the style should be set. +#' @param style_ref List. The style-reference object made with [.style_create]. +#' @noRd .style_notes <- function(wb, content, style_ref) { - tab_name <- content[content$sheet_type == "notes", "tab_title"][[1]] - table <- content[content$sheet_type == "notes", "table"][[1]] + tab_title <- content[content[["sheet_type"]] == "notes", "tab_title"][[1]] + table <- content[content[["sheet_type"]] == "notes", "table"][[1]] + table_height <- nrow(table) table_width <- ncol(table) + start_row <- .get_start_row_table( + content, + tab_title, + .has_notes(content, tab_title), + .has_blanks_message(content, tab_title), + .has_custom_rows(content, tab_title), + .has_source(content, tab_title) + ) + # Notes columns are SET-WIDTH, WRAPPED and LEFT ALIGNED openxlsx::setColWidths( wb = wb, - sheet = tab_name, + sheet = tab_title, cols = 1, widths = 16 ) openxlsx::setColWidths( wb = wb, - sheet = tab_name, + sheet = tab_title, cols = 2, widths = 56 ) openxlsx::addStyle( wb = wb, - sheet = tab_name, - rows = seq(table_height + 1) + 2, + sheet = tab_title, + rows = seq(start_row, table_height + start_row), cols = seq(table_width), gridExpand = TRUE, - style = style_ref$wrap, + style = style_ref[["wrap"]], stack = TRUE ) openxlsx::addStyle( wb = wb, - sheet = tab_name, - rows = seq(table_height + 1) + 2, + sheet = tab_title, + rows = seq(start_row, table_height + start_row), cols = seq(table_width), gridExpand = TRUE, - style = style_ref$lalign, + style = style_ref[["lalign"]], stack = TRUE ) diff --git a/R/utils-workbook.R b/R/utils-workbook.R index 90a4c1c..fb53216 100644 --- a/R/utils-workbook.R +++ b/R/utils-workbook.R @@ -45,6 +45,18 @@ } +.has_custom_rows <- function(content, tab_title) { + + custom_rows <- content[content$tab_title == tab_title, "custom_rows"][[1]] + + if (any(!is.na(custom_rows))) { + TRUE + } else { + FALSE + } + +} + .has_notes <- function(content, tab_title) { table_names <- names(content[content$tab_title == tab_title, "table"][[1]]) @@ -113,9 +125,30 @@ } +.get_start_row_custom_rows <- function( + has_notes, + has_blanks_message, + start_row = 3 +) { + + if (has_notes) { + start_row <- start_row + 1 + } + + if (has_blanks_message) { + start_row <- start_row + 1 + } + + return(start_row) + +} + .get_start_row_source <- function( + content, + tab_title, has_notes, has_blanks_message, + has_custom_rows, start_row = 3 ) { @@ -127,13 +160,21 @@ start_row <- start_row + 1 } + if (has_custom_rows) { + custom_rows <- content[content$tab_title == tab_title, "custom_rows"][[1]] + start_row <- start_row + length(custom_rows) + } + return(start_row) } .get_start_row_table <- function( + content, + tab_title, has_notes, has_blanks_message, + has_custom_rows, has_source, start_row = 3 ) { @@ -146,6 +187,11 @@ start_row <- start_row + 1 } + if (has_custom_rows) { + custom_rows <- content[content$tab_title == tab_title, "custom_rows"][[1]] + start_row <- start_row + length(custom_rows) + } + if (has_source) { start_row <- start_row + 1 } @@ -170,8 +216,7 @@ sheet = tab_title, x = sheet_title, startCol = 1, - startRow = 1, - colNames = TRUE + startRow = 1 ) } @@ -183,8 +228,7 @@ sheet = tab_title, x = sheet_title, startCol = 1, - startRow = 1, - colNames = TRUE + startRow = 1 ) } @@ -222,8 +266,7 @@ sheet = tab_title, x = text, startCol = 1, - startRow = 2, # table count will always be the second row - colNames = TRUE + startRow = 2 # table count will always be the second row ) return(wb) @@ -244,8 +287,7 @@ sheet = tab_title, x = text, startCol = 1, - startRow = 3, # notes will always go in row 3 if they exist - colNames = TRUE + startRow = 3 # notes will always go in row 3 if they exist ) } @@ -280,8 +322,7 @@ sheet = tab_title, x = blanks_text, startCol = 1, - startRow = start_row, # dependent on whether notes text present - colNames = TRUE + startRow = start_row ) } @@ -290,6 +331,39 @@ } +.insert_custom_rows <- function(wb, content, tab_title) { + + has_custom_rows <- .has_custom_rows(content, tab_title) + + if (has_custom_rows) { + + custom_rows_text <- + content[content$tab_title == tab_title, "custom_rows"][[1]] + + custom_rows_text <- lapply(custom_rows_text, .make_hyperlink) + + has_notes <- .has_notes(content, tab_title) + has_blanks <- .has_blanks_message(content, tab_title) + start_row <- .get_start_row_custom_rows(has_notes, has_blanks) + + for (i in seq_along(custom_rows_text)) { + + openxlsx::writeData( + wb = wb, + sheet = tab_title, + x = custom_rows_text[[i]], + startCol = 1, + startRow = start_row + (i - 1) + ) + + } + + } + + return(wb) + +} + .insert_source <- function(wb, content, tab_title) { has_source <- .has_source(content, tab_title) @@ -298,24 +372,22 @@ source_text <- content[content$tab_title == tab_title, "source"][[1]] source_text <- paste("Source:", source_text) - - source_has_hyperlink <- .detect_hyperlink(source_text) - - if (source_has_hyperlink) { - source_text <- .make_hyperlink(source_text) - } - - has_notes <- .has_notes(content, tab_title) - has_blanks_message <- .has_blanks_message(content, tab_title) - start_row <- .get_start_row_source(has_notes, has_blanks_message) + source_text <- .make_hyperlink(source_text) + + start_row <- .get_start_row_source( + content, + tab_title, + .has_notes(content, tab_title), + .has_blanks_message(content, tab_title), + .has_custom_rows(content, tab_title) + ) openxlsx::writeData( wb = wb, sheet = tab_title, x = source_text, startCol = 1, - startRow = start_row # dependent on whether notes text present - # colNames = TRUE + startRow = start_row ) } @@ -330,21 +402,14 @@ sheet_type <- content[content$table_name == table_name, "sheet_type"][[1]] tab_title <- content[content$table_name == table_name, "tab_title"][[1]] - has_notes <- .has_notes(content, tab_title) - has_blanks_message <- .has_blanks_message(content, tab_title) - has_source <- .has_source(content, tab_title) - - if (sheet_type %in% c("contents", "notes")) { - start_row <- 3 - } - - if (sheet_type == "tables") { - start_row <- .get_start_row_table( - has_notes, - has_blanks_message, - has_source - ) - } + start_row <- .get_start_row_table( + content, + tab_title, + .has_notes(content, tab_title), + .has_blanks_message(content, tab_title), + .has_custom_rows(content, tab_title), + .has_source(content, tab_title) + ) openxlsx::writeDataTable( wb = wb, @@ -352,8 +417,7 @@ x = table, tableName = table_name, startCol = 1, - startRow = start_row, # dependent on whether notes or source text present - colNames = TRUE, + startRow = start_row, tableStyle = "none", withFilter = FALSE, bandedRows = FALSE @@ -447,7 +511,7 @@ md_match <- regexpr(md_rx, string, perl = TRUE) md_extract <- regmatches(string, md_match)[[1]] - url_rx <- "(?<=\\()([[:graph:]]|[[:space:]])+(?=\\))" + url_rx <- "(?<=\\]\\()([[:graph:]]|[[:space:]])+(?=\\))" url_match <- regexpr(url_rx, md_extract, perl = TRUE) url_extract <- regmatches(md_extract, url_match)[[1]] @@ -529,6 +593,7 @@ .insert_title(wb, content, tab_title) .insert_table_count(wb, content, tab_title) + .insert_custom_rows(wb, content, tab_title) .insert_table(wb, content, table_name) styles <- .style_create() @@ -551,6 +616,7 @@ .insert_title(wb, content, tab_title) .insert_table_count(wb, content, tab_title) + .insert_custom_rows(wb, content, tab_title) .insert_table(wb, content, table_name) styles <- .style_create() @@ -574,6 +640,7 @@ .insert_source(wb, content, tab_title) .insert_notes_statement(wb, content, tab_title) .insert_blanks_message(wb, content, tab_title) + .insert_custom_rows(wb, content, tab_title) .insert_table(wb, content, table_name) styles <- .style_create() diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 0000000..bcc0100 --- /dev/null +++ b/R/utils.R @@ -0,0 +1,16 @@ + +#' Convert a List to A Sentence Form +#' Vectors of 1, 2 and 3 letters become 'A', 'A and B', 'A, B and C'. +#' @noRd +.vector_to_sentence <- function(vector) { + + if (length(vector) > 1) { + last <- vector[length(vector)] + not_last <- vector[-length(vector)] + sentence <- paste(paste(not_last, collapse = ", "), "and", last) + return(sentence) + } + + vector + +} diff --git a/R/workbook.R b/R/workbook.R index dbc6f67..62c7c3b 100644 --- a/R/workbook.R +++ b/R/workbook.R @@ -12,23 +12,15 @@ #' @return A Workbook-class object. #' #' @examples -#' # Create an a11ytable with in-built demo dataframe, mtcars_df2. We can use -#' # 'as_a11ytable' rather than 'create_a11ytable' because the data is already -#' # in the right format. -#' x <- as_a11ytable(mtcars_df2) +#' # Convert an a11ytable to a Workbook-class object +#' x <- generate_workbook(demo_a11ytable) +#' class(x) #' -#' # Convert to a Workbook-class object -#' y <- generate_workbook(x) -#' class(y) -#' -#' # As above, using a base pipe -#' z <- mtcars_df2 |> +#' # As above, using a compliant data.frame and the base pipe +#' y <- demo_df |> #' as_a11ytable() |> #' generate_workbook() #' -#' # You can also use the RStudio Addin installed with the package to insert a -#' # an example skeleton containing this function. -#' #' @export generate_workbook <- function(a11ytable) { @@ -36,7 +28,7 @@ generate_workbook <- function(a11ytable) { stop("The object passed to argument 'content' must have class 'a11ytable'.") } - # Create a table_name from tab_title (unqiue, no spaces, no punctuation) + # Create a table_name from tab_title (unique, no spaces, no punctuation) a11ytable[["table_name"]] <- gsub(" ", "_", tolower(trimws(a11ytable[["tab_title"]]))) a11ytable[["table_name"]] <- diff --git a/_pkgdown.yml b/_pkgdown.yml index 1527012..2a1c1f6 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -19,24 +19,23 @@ footer: legal: © Crown Copyright, 2023, Cabinet Office reference: -- title: "a11ytables" - desc: "Create, coerce and inspect a11ytable-class objects" - contents: - - as_a11ytable - - create_a11ytable - - is_a11ytable - - summary.a11ytable - - tbl_sum.a11ytable -- title: "Workbooks" - desc: "Convert a11ytable- to Workbook-class objects" - contents: - - generate_workbook -- title: "Data" - desc: "Demo datasets" - contents: - - mtcars_df - - mtcars_df2 -- title: "RStudio Addins" - desc: "RStudio Addin functions for inserting workflow skeletons" - contents: - - starts_with("at_") + - title: "a11ytables" + desc: "Create, coerce and inspect a11ytable-class objects" + contents: + - as_a11ytable + - create_a11ytable + - is_a11ytable + - summary.a11ytable + - tbl_sum.a11ytable + - title: "Workbooks" + desc: "Convert a11ytable- to Workbook-class objects" + contents: generate_workbook + - title: "Data" + desc: "Demo datasets used in package examples" + contents: starts_with("demo_") + - title: "RStudio Addins" + desc: "RStudio Addin functions for inserting workflow skeletons" + contents: starts_with("at_") + - title: Superseded + desc: "Demo datasets superseded in version 0.3 of the package" + contents: starts_with("mtcars_df") diff --git a/a11ytables.Rproj b/a11ytables.Rproj index 69fafd4..38b9011 100644 --- a/a11ytables.Rproj +++ b/a11ytables.Rproj @@ -1,22 +1,22 @@ -Version: 1.0 - -RestoreWorkspace: No -SaveWorkspace: No -AlwaysSaveHistory: Default - -EnableCodeIndexing: Yes -UseSpacesForTab: Yes -NumSpacesForTab: 2 -Encoding: UTF-8 - -RnwWeave: Sweave -LaTeX: pdfLaTeX - -AutoAppendNewline: Yes -StripTrailingWhitespace: Yes -LineEndingConversion: Posix - -BuildType: Package -PackageUseDevtools: Yes -PackageInstallArgs: --no-multiarch --with-keep.source -PackageRoxygenize: rd,collate,namespace +Version: 1.0 + +RestoreWorkspace: No +SaveWorkspace: No +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +AutoAppendNewline: Yes +StripTrailingWhitespace: Yes +LineEndingConversion: Posix + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace diff --git a/data-raw/data-mtcars.R b/data-raw/data.R similarity index 53% rename from data-raw/data-mtcars.R rename to data-raw/data.R index ec482c6..a7b6eec 100644 --- a/data-raw/data-mtcars.R +++ b/data-raw/data.R @@ -1,4 +1,95 @@ -# This file generates and writes the dataset 'mtcars_df' and 'mtcars_df2' +# This file generates and writes demo datasets + + +# demo_df and demo_a11ytable (as of v0.3) --------------------------------- + + +set.seed(1066) + +cover_list <- list( + "Section 1" = c("First row of Section 1.", "Second row of Section 1."), + "Section 2" = "The only row of Section 2.", + "Section 3" = c( + "[Website](https://co-analysis.github.io/a11ytables/)", + "[Email address](mailto:fake.address@a11ytables.com)" + ) +) + +contents_df <- data.frame( + "Sheet name" = c("Notes", "Table 1", "Table 2"), + "Sheet title" = c( + "Notes used in this workbook", + "First Example Sheet", + "Second Example Sheet" + ), + check.names = FALSE +) + +notes_df <- data.frame( + "Note number" = paste0("[note ", 1:3, "]"), + "Note text" = c("First note.", "Second note.", "Third note."), + check.names = FALSE +) + +table_1_df <- data.frame( + Category = LETTERS[1:10], + "Numeric [note 1]" = 1:10, + "Numeric suppressed" = c(1:4, "[c]", 6:9, "[x]"), + "Numeric thousands" = abs(round(rnorm(10), 4) * 1e5), + "Numeric decimal" = abs(round(rnorm(10), 5)), + "This column has a very long name that means that the column width needs to be widened" = 1:10, + Notes = c("[note 2]", rep(NA_character_, 4), "[note 3]", rep(NA_character_, 4)), + check.names = FALSE +) + +table_2_df <- data.frame(Category = LETTERS[1:10], Numeric = 1:10) + +demo_a11ytable <- + a11ytables::create_a11ytable( + tab_titles = c("Cover", "Contents", "Notes", "Table_1", "Table_2"), + sheet_types = c("cover", "contents", "notes", "tables", "tables"), + sheet_titles = c( + "The 'a11ytables' Demo Workbook", + "Table of contents", + "Notes", + "Table_1: First Example Sheet", + "Table_2: Second Example Sheet" + ), + blank_cells = c( + rep(NA_character_, 3), + "Blank cells indicate that there's no note in that row.", + NA_character_ + ), + custom_rows = list( + NA_character_, + NA_character_, + "A custom row.", + c( + "First custom row [with a hyperlink.](https://co-analysis.github.io/a11ytables/)", + "Second custom row." + ), + "A custom row." + ), + sources = c( + rep(NA_character_, 3), + "[The Source Material, 2024.](https://co-analysis.github.io/a11ytables/)", + "The Source Material, 2024." + ), + tables = list(cover_list, contents_df, notes_df, table_1_df, table_2_df) + ) + +demo_df <- as.data.frame(demo_a11ytable) + +demo_workbook <- generate_workbook(demo_a11ytable) + +# Write to data/ +usethis::use_data(demo_df, overwrite = TRUE) +usethis::use_data(demo_a11ytable, overwrite = TRUE) +usethis::use_data(demo_workbook, overwrite = TRUE) + + +# mtcars_df and mtcars_df2 (superseded in v0.3) -------------------------- + library(tibble) @@ -80,7 +171,7 @@ mtcars_df <- tibble( table = list(cover_df, contents_df, notes_df, stats_df_1, stats_df_2) ) -# Using cover_list as the table input for the cover +# Using cover_list as the table input for the cover, introduced in v0.2 mtcars_df2 <- tibble( tab_title = c("Cover", "Contents", "Notes", "Table_1", "Table_2"), sheet_type = c("cover", "contents", "notes", "tables", "tables"), diff --git a/data/demo_a11ytable.rda b/data/demo_a11ytable.rda new file mode 100644 index 0000000..b3b9840 Binary files /dev/null and b/data/demo_a11ytable.rda differ diff --git a/data/demo_df.rda b/data/demo_df.rda new file mode 100644 index 0000000..980d300 Binary files /dev/null and b/data/demo_df.rda differ diff --git a/data/demo_workbook.rda b/data/demo_workbook.rda new file mode 100644 index 0000000..295619e Binary files /dev/null and b/data/demo_workbook.rda differ diff --git a/inst/rstudio/addins.dcf b/inst/rstudio/addins.dcf index 6d79abe..447c04b 100644 --- a/inst/rstudio/addins.dcf +++ b/inst/rstudio/addins.dcf @@ -4,23 +4,9 @@ Description: Insert at the cursor a template for create_a11ytable() from the Binding: at_template_a11ytable Interactive: false -Name: Insert Table Templates Using 'tibble' -Description: Insert at the cursor templates for cover, contents and notes - tables, pre-filled with example information. Requires the 'tibble' package - to be installed. -Binding: at_template_tibble -Interactive: false - -Name: Insert Table Templates Using 'data.frame' -Description: Insert at the cursor templates for cover, contents and notes - tables, pre-filled with example information. Uses \code{\link[data.frame}}, - so isn't dependent on external packages. -Binding: at_template_df -Interactive: false - Name: Insert Full 'a11ytables' Template Workflow -Description: Insert at the cursor templates for cover, contents and notes - tables, and create_a11ytable(), which are all pre-filled with example +Description: Insert at the cursor (a) templates for cover, contents and notes + tables, and (b) create_a11ytable(), which are all pre-filled with example information. Binding: at_template_workflow Interactive: false diff --git a/man/as_a11ytable.Rd b/man/as_a11ytable.Rd index 85fb19e..304e66c 100644 --- a/man/as_a11ytable.Rd +++ b/man/as_a11ytable.Rd @@ -21,12 +21,6 @@ a11ytable, otherwise \code{FALSE}. Functions to check if an object is an a11ytable, or coerce it if possible. } \examples{ -# Create an a11ytable with in-built demo dataframe, mtcars_df2. We can use -# 'as_a11ytable' rather than 'create_a11ytable' because the data is already -# in the right format. -x <- as_a11ytable(mtcars_df2) - -# Test the object's class -is_a11ytable(x) +is_a11ytable(demo_a11ytable) } diff --git a/man/at_template_a11ytable.Rd b/man/at_template_a11ytable.Rd index dc73284..92caaab 100644 --- a/man/at_template_a11ytable.Rd +++ b/man/at_template_a11ytable.Rd @@ -2,11 +2,11 @@ % Please edit documentation in R/addin.R \name{at_template_a11ytable} \alias{at_template_a11ytable} -\title{Insert 'a11ytable' Template} +\title{Insert Demo 'create_a11ytable' Template} \usage{ at_template_a11ytable() } \description{ Insert at the cursor a template for \code{\link{create_a11ytable}} from the -'a11ytable' package, pre-filled with example information. +'a11ytable' package, pre-filled with demo data. } diff --git a/man/at_template_df.Rd b/man/at_template_df.Rd deleted file mode 100644 index a0cd5b3..0000000 --- a/man/at_template_df.Rd +++ /dev/null @@ -1,13 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/addin.R -\name{at_template_df} -\alias{at_template_df} -\title{Insert Table Templates Using 'data.frame'} -\usage{ -at_template_df() -} -\description{ -Insert at the cursor templates for cover, contents and notes -tables, pre-filled with example information. Uses \code{\link{data.frame}}, -so isn't dependent on external packages. -} diff --git a/man/at_template_tibble.Rd b/man/at_template_tibble.Rd deleted file mode 100644 index 77a83f1..0000000 --- a/man/at_template_tibble.Rd +++ /dev/null @@ -1,13 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/addin.R -\name{at_template_tibble} -\alias{at_template_tibble} -\title{Insert Table Templates Using 'tibble'} -\usage{ -at_template_tibble() -} -\description{ -Insert at the cursor templates for cover, contents and notes tables, -pre-filled with example information. Requires the 'tibble' package to be -installed. -} diff --git a/man/at_template_workflow.Rd b/man/at_template_workflow.Rd index 53e3660..9c1d2a4 100644 --- a/man/at_template_workflow.Rd +++ b/man/at_template_workflow.Rd @@ -2,12 +2,12 @@ % Please edit documentation in R/addin.R \name{at_template_workflow} \alias{at_template_workflow} -\title{Insert Full 'a11ytables' Template Workflow} +\title{Insert Full Demo 'a11ytables' Template Workflow} \usage{ at_template_workflow() } \description{ -Insert at the cursor templates for cover, contents and notes -tables, and \code{\link{create_a11ytable}}, which are all pre-filled with -example information. +Insert at the cursor (a) demo templates for cover, contents and notes +tables, and (b) a call to \code{\link{create_a11ytable}} pre-filled with +demo data. } diff --git a/man/create_a11ytable.Rd b/man/create_a11ytable.Rd index a08f0a3..82dff9c 100644 --- a/man/create_a11ytable.Rd +++ b/man/create_a11ytable.Rd @@ -10,6 +10,7 @@ create_a11ytable( sheet_titles, blank_cells = NA_character_, sources = NA_character_, + custom_rows = list(NA_character_), tables ) } @@ -29,13 +30,20 @@ data are type 'tables'.} title for each sheet, which will appear in cell A1 (top-left corner).} \item{blank_cells}{Optional character vector, one value per sheet. A short -sentence to explain the reason for any blank cells in the sheet. Most -likely to be used with sheet type 'tables'.} +sentence to explain the reason for any blank cells in the sheet. Supply +as \code{NA_character_} if empty. Most likely to be used with sheet type +'tables'.} \item{sources}{Optional character vector, one value per sheet. The origin of the data for a given sheet. Supply as \code{NA_character_} if empty. To be used with sheet type 'tables'.} +\item{custom_rows}{Optional list of character vectors. One list element per +sheet, one character vector element per row of pre-table metadata. Supply +a list element as \code{NA_character_} if empty. To be used with sheet +type 'tables', but can also be used for sheet types 'contents' and +'notes'.} + \item{tables}{Required list of data.frames (though the cover sheet may be supplied as a list), one per sheet. See details.} } @@ -43,9 +51,9 @@ supplied as a list), one per sheet. See details.} An object with classes 'a11ytable', 'tbl' and 'data.frame'. } \description{ -Create a new a11ytable-class object, which is a dataframe that contains all -the information needed in your output spreadsheet. In turn, the object -created by this function can be used to populate an 'openxlsx' +Create a new a11ytable-class object, which is a special data.frame that +contains all the information needed in your output spreadsheet. In turn, the +object created by this function can be used to populate an 'openxlsx' Workbook-class object with the function \code{\link{generate_workbook}}. } \details{ @@ -65,9 +73,10 @@ the title 'Contact details' that contains an email address and telephone number. You can use linebreaks (i.e. '\\n') to separate text into paragraphs. \item Sheet type 'contents': one row per sheet, two columns suggested at -least ('Tab title' and 'Worksheet title'). -\item Sheet type 'notes': one row per note, two columns suggested ('Note -number', 'Note text'), where notes are in the form '[note 1]'. +least (named 'Tab title' and 'Worksheet title'). +\item Sheet type 'notes': one row per note, two columns suggested (named +'Note number', 'Note text'), where notes are in the form +'[note 1]'. \item Sheet type 'tables': a tidy, rectangular data.frame containing the data to be published. It's the user's responsibility to add notes in the form '[note 1]' to column headers, or in a special 'Notes' row. @@ -82,35 +91,102 @@ email address) and the containing cell will be rendered as a hyperlink in the output spreadsheet. Note that whole cells will become hyperlinks; there is no support for selected words in a sentence to be rendered as a hyperlink. -Hyperlinks can be supplied in two locations: +Hyperlinks can be supplied in the character strings to three arguments: \itemize{ \item To the 'tables' argument for sheet type 'cover' only. It's recommended to supply the cover information as a list rather than a data.frame, which will allow you to make specific rows within a -section into hyperlinks. For example, in a 'Contact us' section you -might want a row containing some preamble (no hyperlink), a cell -containing a phone number (no hyperlink) and a cell containing an -email address (hyperlinked). -\item To the 'source' argument for data tables. +section (e.g. 'contact us') into hyperlinks. +\item To the 'custom_rows' argument for sheets of type 'contents, 'notes' +and 'tables'. +\item To the 'source' argument for sheets of type 'table' only. } } } \examples{ -# Create an a11ytable with in-built demo dataframe, mtcars_df2 -x <- create_a11ytable( - tab_titles = mtcars_df2$tab_title, - sheet_types = mtcars_df2$sheet_type, - sheet_titles = mtcars_df2$sheet_title, - blank_cells = mtcars_df2$blank_cells, - sources = mtcars_df2$source, - tables = mtcars_df2$table +# Prepare some demo tables of information + +set.seed(1066) + +cover_list <- list( + "Section 1" = c("First row of Section 1.", "Second row of Section 1."), + "Section 2" = "The only row of Section 2.", + "Section 3" = c( + "[Website](https://co-analysis.github.io/a11ytables/)", + "[Email address](mailto:fake.address@a11ytables.com)" + ) +) + +contents_df <- data.frame( + "Sheet name" = c("Notes", "Table_1", "Table_2"), + "Sheet title" = c( + "Notes used in this workbook", + "First Example Sheet", + "Second Example Sheet" + ), + check.names = FALSE +) + +notes_df <- data.frame( + "Note number" = paste0("[note ", 1:3, "]"), + "Note text" = c("First note.", "Second note.", "Third note."), + check.names = FALSE +) + +table_1_df <- data.frame( + Category = LETTERS[1:10], + "Numeric [note 1]" = 1:10, + "Numeric suppressed" = c(1:4, "[c]", 6:9, "[x]"), + "Numeric thousands" = abs(round(rnorm(10), 4) * 1e5), + "Numeric decimal" = abs(round(rnorm(10), 5)), + "This column has a very long name that means that the column width needs to be widened" = 1:10, + Notes = c("[note 1]", rep(NA_character_, 4), "[note 2]", rep(NA_character_, 4)), + check.names = FALSE ) +table_2_df <- data.frame(Category = LETTERS[1:10], Numeric = 1:10) + +# Create 'a11ytables' object + +x <- + a11ytables::create_a11ytable( + tab_titles = c("Cover", "Contents", "Notes", "Table_1", "Table_2"), + sheet_types = c("cover", "contents", "notes", "tables", "tables"), + sheet_titles = c( + "The 'a11ytables' Demo Workbook", + "Table of contents", + "Notes", + "Table 1: First Example Sheet", + "Table 2: Second Example Sheet" + ), + blank_cells = c( + rep(NA_character_, 3), + "Blank cells indicate that there's no note in that row.", + NA_character_ + ), + custom_rows = list( + NA_character_, + NA_character_, + "A custom row.", + c( + "First custom row [with a hyperlink.](https://co-analysis.github.io/a11ytables/)", + "Second custom row." + ), + "A custom row." + ), + sources = c( + rep(NA_character_, 3), + "[The Source Material, 2024.](https://co-analysis.github.io/a11ytables/)", + "The Source Material, 2024." + ), + tables = list(cover_list, contents_df, notes_df, table_1_df, table_2_df) + ) + # Test that 'a11ytable' is one of the object's classes is_a11ytable(x) -# You can also use the RStudio Addin installed with the package to insert a -# an example skeleton containing this function. +# Look at the structure of the object +str(x, max.level = 2) } diff --git a/man/demo_a11ytable.Rd b/man/demo_a11ytable.Rd new file mode 100644 index 0000000..1b861bb --- /dev/null +++ b/man/demo_a11ytable.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data.R +\docType{data} +\name{demo_a11ytable} +\alias{demo_a11ytable} +\title{A Demo 'a11ytables' Object} +\format{ +A data.frame with 6 rows and 7 columns: +\describe{ +\item{tab_title}{Character. Text to appear on each sheet's tab.} +\item{sheet_type}{Character. The content type for each sheet: 'cover', 'contents', 'notes', or 'tables'.} +\item{sheet_title}{Character. The title that will appear in cell A1 (top-left) of each sheet.} +\item{blank_cells}{Character. An explanation for any blank cells in the table.} +\item{custom_rows}{List-column of character vectors. Additional arbitrary pre-table information provided by the user.} +\item{source}{Character. The origin of the data, if relevant.} +\item{table}{List-column of data.frames (apart from the cover, which is a list) containing the statistical tables.} +} +} +\usage{ +demo_a11ytable +} +\description{ +A pre-created 'a11ytables' object ready to be converted to an 'openxlsx' +Workbook-class object with \code{\link{generate_workbook}}. +} +\keyword{datasets} diff --git a/man/demo_df.Rd b/man/demo_df.Rd new file mode 100644 index 0000000..8acdf09 --- /dev/null +++ b/man/demo_df.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data.R +\docType{data} +\name{demo_df} +\alias{demo_df} +\title{A Demo 'data.frame' Object} +\format{ +A data.frame with 6 rows and 7 columns: +\describe{ +\item{tab_title}{Character. Text to appear on each sheet's tab.} +\item{sheet_type}{Character. The content type for each sheet: 'cover', 'contents', 'notes', or 'tables'.} +\item{sheet_title}{Character. The title that will appear in cell A1 (top-left) of each sheet.} +\item{blank_cells}{Character. An explanation for any blank cells in the table.} +\item{custom_rows}{List-column of character vectors. Additional arbitrary pre-table information provided by the user.} +\item{source}{Character. The origin of the data, if relevant.} +\item{table}{List-column of data.frames (apart from the cover, which is a list) containing the statistical tables.} +} +} +\usage{ +demo_df +} +\description{ +A pre-created data.frame ready to be converted to an a11ytables-class object +with \code{\link{as_a11ytable}} and then an 'openxlsx' Workbook-class object +with \code{\link{generate_workbook}}. +} +\keyword{datasets} diff --git a/man/demo_workbook.Rd b/man/demo_workbook.Rd new file mode 100644 index 0000000..3d6868a --- /dev/null +++ b/man/demo_workbook.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data.R +\docType{data} +\name{demo_workbook} +\alias{demo_workbook} +\title{A Demo 'Workbook' Object} +\format{ +An 'openxlsx' Workbook-class object with 5 sheets. +} +\usage{ +demo_workbook +} +\description{ +A pre-created 'openxlsx' Workbook'-class object generated from an +a11ytables-class object with \code{\link{generate_workbook}}. +} +\keyword{datasets} diff --git a/man/figures/screenshot-1.png b/man/figures/screenshot-1.png index 7de02f4..bc701e0 100644 Binary files a/man/figures/screenshot-1.png and b/man/figures/screenshot-1.png differ diff --git a/man/figures/screenshot-2.png b/man/figures/screenshot-2.png index 4463643..c376818 100644 Binary files a/man/figures/screenshot-2.png and b/man/figures/screenshot-2.png differ diff --git a/man/figures/screenshot-3.png b/man/figures/screenshot-3.png index 7f5136f..78f5a63 100644 Binary files a/man/figures/screenshot-3.png and b/man/figures/screenshot-3.png differ diff --git a/man/figures/screenshot-4.png b/man/figures/screenshot-4.png index 84d76cf..648beb6 100644 Binary files a/man/figures/screenshot-4.png and b/man/figures/screenshot-4.png differ diff --git a/man/figures/screenshot-5.png b/man/figures/screenshot-5.png new file mode 100644 index 0000000..0b979e7 Binary files /dev/null and b/man/figures/screenshot-5.png differ diff --git a/man/figures/vignette-workbook.gif b/man/figures/vignette-workbook.gif index db8c1e3..55b91f6 100644 Binary files a/man/figures/vignette-workbook.gif and b/man/figures/vignette-workbook.gif differ diff --git a/man/generate_workbook.Rd b/man/generate_workbook.Rd index 07e43a4..df620ad 100644 --- a/man/generate_workbook.Rd +++ b/man/generate_workbook.Rd @@ -20,21 +20,13 @@ a11ytable-class object. In turn, the output can be passed to \code{\link[openxlsx]{saveWorkbook}} from 'openxlsx' } \examples{ -# Create an a11ytable with in-built demo dataframe, mtcars_df2. We can use -# 'as_a11ytable' rather than 'create_a11ytable' because the data is already -# in the right format. -x <- as_a11ytable(mtcars_df2) +# Convert an a11ytable to a Workbook-class object +x <- generate_workbook(demo_a11ytable) +class(x) -# Convert to a Workbook-class object -y <- generate_workbook(x) -class(y) - -# As above, using a base pipe -z <- mtcars_df2 |> +# As above, using a compliant data.frame and the base pipe +y <- demo_df |> as_a11ytable() |> generate_workbook() -# You can also use the RStudio Addin installed with the package to insert a -# an example skeleton containing this function. - } diff --git a/man/mtcars_df.Rd b/man/mtcars_df.Rd index ccafb16..8365ccd 100644 --- a/man/mtcars_df.Rd +++ b/man/mtcars_df.Rd @@ -3,13 +3,13 @@ \docType{data} \name{mtcars_df} \alias{mtcars_df} -\title{Test Data: A Modified 'mtcars' Dataframe} +\title{Test Data: A Modified 'mtcars' Dataframe (Version 1)} \format{ -A data frame with 5 rows and 7 columns: +A data frame with 5 rows and 6 columns: \describe{ -\item{tab_title}{Character. Text to appear on the sheet's tab.} +\item{tab_title}{Character. Text to appear on each sheet's tab.} \item{sheet_type}{Character. The content type for each sheet: 'cover', 'contents', 'notes', or 'tables'.} -\item{sheet_title}{Character. The title that will appear in the top-left of each sheet.} +\item{sheet_title}{Character. The title that will appear in cell A1 (top-left) of each sheet.} \item{blank_cells}{Character. An explanation for any blank cells in the table.} \item{source}{Character. The origin of the data, if relevant.} \item{table}{List-column of data.frames containing the statistical tables.} @@ -22,9 +22,20 @@ A data frame with 5 rows and 7 columns: mtcars_df } \description{ +Superseded. mtcars_df and \code{\link{mtcars_df2}} have been superseded in +favour of \code{\link{demo_df}}. + A modified version of the mtcars dataset prepared into a data.frame structure ready for coercion to an a11ytables-class object with \code{\link{as_a11ytable}}. Uses a dataframe as input to the cover table; \code{\link{mtcars_df}} uses a list as input to the cover table. } +\details{ +Uses a data.frame as input to the cover table, whereas +\code{\link{mtcars_df2}} uses a list as input to the cover table +(implemented in version 0.2). + +Note that this dataset is superseded by \code{\link{demo_df}} but is +retained for backwards-compatibility with package versions prior to 0.3. +} \keyword{datasets} diff --git a/man/mtcars_df2.Rd b/man/mtcars_df2.Rd index f719e6d..f2a1c61 100644 --- a/man/mtcars_df2.Rd +++ b/man/mtcars_df2.Rd @@ -3,13 +3,13 @@ \docType{data} \name{mtcars_df2} \alias{mtcars_df2} -\title{Test Data: A Modified 'mtcars' Dataframe} +\title{Test Data: A Modified 'mtcars' Dataframe (Version 2)} \format{ -A data frame with 5 rows and 7 columns: +A data frame with 5 rows and 6 columns: \describe{ -\item{tab_title}{Character. Text to appear on the sheet's tab.} +\item{tab_title}{Character. Text to appear on each sheet's tab.} \item{sheet_type}{Character. The content type for each sheet: 'cover', 'contents', 'notes', or 'tables'.} -\item{sheet_title}{Character. The title that will appear in the top-left of each sheet.} +\item{sheet_title}{Character. The title that will appear in cell A1 (top-left) of each sheet.} \item{blank_cells}{Character. An explanation for any blank cells in the table.} \item{source}{Character. The origin of the data, if relevant.} \item{table}{List-column of data.frames (apart from the cover, which is a list) containing the statistical tables.} @@ -22,9 +22,18 @@ A data frame with 5 rows and 7 columns: mtcars_df2 } \description{ +Superseded. \code{\link{mtcars_df}} and mtcars_df2 have been superseded in +favour of \code{\link{demo_df}}. + A modified version of the mtcars dataset prepared into a data.frame structure ready for coercion to an a11ytables-class object with -\code{\link{as_a11ytable}}. Uses a list as input to the cover table; -\code{\link{mtcars_df}} uses a dataframe as input to the cover table. +\code{\link{as_a11ytable}}. +} +\details{ +Uses a list as input to the cover table (implemented in version 0.2), whereas +\code{\link{mtcars_df}} uses a data.frame as input to the cover table. + +Note that this dataset is superseded by \code{\link{demo_df}} but is +retained for backwards-compatibility with package versions starting 0.2. } \keyword{datasets} diff --git a/man/summary.a11ytable.Rd b/man/summary.a11ytable.Rd index 22fe8e9..946c712 100644 --- a/man/summary.a11ytable.Rd +++ b/man/summary.a11ytable.Rd @@ -12,16 +12,15 @@ \item{...}{Other arguments to pass.} } \description{ -A concise result summary of an a11ytable-class object to see information about -the sheet content. +A concise result summary of an a11ytable-class object to see information +about the sheet content. Shows a numbered list of sheets with each tab title, +sheet type and table dimensions. } \examples{ -# Create an a11ytable with in-built demo dataframe, mtcars_df2. We can use -# 'as_a11ytable' rather than 'create_a11ytable' because the data is already -# in the right format. -x <- as_a11ytable(mtcars_df2) +# Print a concise summary of the a11ytable-class object +summary(demo_a11ytable) -# Print summary of a11ytable-class object -summary(x) +# Alternatively, look at the structure +str(demo_a11ytable, max.level = 2) } diff --git a/man/tbl_sum.a11ytable.Rd b/man/tbl_sum.a11ytable.Rd index 321b652..ea27a93 100644 --- a/man/tbl_sum.a11ytable.Rd +++ b/man/tbl_sum.a11ytable.Rd @@ -15,20 +15,13 @@ Named character vector. } \description{ -A brief textual description of an a11ytable-class object. +A brief text description of an a11ytable-class object. } \examples{ -\dontrun{ -# Create an a11ytable with in-built demo dataframe, mtcars_df2. We can use -# 'as_a11ytable' rather than 'create_a11ytable' because the data is already -# in the right format. -x <- as_a11ytable(mtcars_df2) - -# Print description only -tbl_sum(x) - # Print with description -print(x) -} +print(demo_a11ytable) + +# Print description only (package 'tibble' must be installed) +tibble::tbl_sum(demo_a11ytable) } diff --git a/tests/testthat/_snaps/a11ytable.md b/tests/testthat/_snaps/a11ytable.md index d4f6b8b..1454303 100644 --- a/tests/testthat/_snaps/a11ytable.md +++ b/tests/testthat/_snaps/a11ytable.md @@ -1,9 +1,9 @@ # tbl output looks as intended - # a11ytable: 3 x 6 - tab_title sheet_type sheet_title blank_cells source table - - 1 A cover A - 2 B contents B - 3 C tables C x. + # a11ytable: 3 x 7 + tab_title sheet_type sheet_title blank_cells source custom_rows table + + 1 A cover A + 2 B contents B + 3 C tables C Source diff --git a/tests/testthat/_snaps/addins.md b/tests/testthat/_snaps/addins.md index 0bd94d0..e114087 100644 --- a/tests/testthat/_snaps/addins.md +++ b/tests/testthat/_snaps/addins.md @@ -1,12 +1,8 @@ # string_create_a11ytable skeleton is okay - [1] "my_a11ytable <-\n a11ytables::create_a11ytable(\n tab_titles = c(\n \"Cover\",\n \"Contents\",\n \"Notes\",\n \"Table_1\"\n ),\n sheet_types = c(\n \"cover\",\n \"contents\",\n \"notes\",\n \"tables\"\n ),\n sheet_titles = c(\n \"Cover title (example)\",\n \"Contents\",\n \"Notes\",\n \"Example sheet title\"\n ),\n blank_cells = c(\n NA_character_,\n NA_character_,\n NA_character_,\n \"Blank cells mean that a row does not have a note.\"\n ),\n sources = c(\n NA_character_,\n NA_character_,\n NA_character_,\n \"Example source.\"\n ),\n tables = list(\n cover_df,\n contents_df,\n notes_df,\n table_df\n )\n )" + [1] "my_a11ytable <-\n a11ytables::create_a11ytable(\n tab_titles = c(\"Cover\", \"Contents\", \"Notes\", \"Table_1\", \"Table_2\"),\n sheet_types = c(\"cover\", \"contents\", \"notes\", \"tables\", \"tables\"),\n sheet_titles = c(\n \"The 'a11ytables' Demo Workbook\",\n \"Table of contents\",\n \"Notes\",\n \"Table 1: First Example Sheet\",\n \"Table 2: Second Example Sheet\"\n ),\n blank_cells = c(\n rep(NA_character_, 3),\n \"Blank cells indicate that there's no note in that row.\",\n NA_character_\n ),\n custom_rows = list(\n NA_character_,\n \"A custom row in the Contents sheet.\",\n NA_character_,\n c(\n \"First custom row for Table 1.\",\n \"A second custom row [with a hyperlink.](https://co-analysis.github.io/a11ytables/)\"\n ),\n \"A custom row for Table 2\"\n ),\n sources = c(\n rep(NA_character_, 3),\n \"[The Source Material, 2024](https://co-analysis.github.io/a11ytables/)\",\n \"The Source Material, 2024\"\n ),\n tables = list(cover_list, contents_df, notes_df, table_1_df, table_2_df)\n )" # string_tables_tibble skeleton is okay - [1] "cover_df <- tibble::tribble(\n ~subsection_title, ~subsection_content,\n \"Purpose\", \"Example results for something.\",\n \"Workbook properties\", \"Some placeholder information.\",\n \"Contact\", \"Placeholder email\"\n )\n\n contents_df <- tibble::tribble(\n ~\"Sheet name\", ~\"Sheet title\",\n \"Notes\", \"Notes\",\n \"Table_1\", \"Example sheet title\"\n )\n\n notes_df <- tibble::tribble(\n ~\"Note number\", ~\"Note text\",\n \"[note 1]\", \"Placeholder note.\",\n \"[note 2]\", \"Placeholder note.\"\n )\n\n table_df <- mtcars\n table_df[[\"car [note 1]\"]] <- row.names(mtcars)\n row.names(table_df) <- NULL\n table_df <- table_df[1:5, c(\"car [note 1]\", \"mpg\", \"cyl\")]\n table_df[\"Notes\"] <- c(\"[note 2]\", rep(NA_character_, 4))" - -# string_tables_df skeleton is okay - - [1] "cover_df <- data.frame(\n subsection_title = c(\n \"Purpose\",\n \"Workbook properties\",\n \"Contact\"\n ),\n subsection_content = c(\n \"Example results for something.\",\n \"Some placeholder information.\",\n \"Placeholder email\"\n ),\n check.names = FALSE\n )\n\n contents_df <- data.frame(\n \"Sheet name\" = c(\"Notes\", \"Table_1\"),\n \"Sheet title\" = c(\"Notes\", \"Example sheet title\"),\n check.names = FALSE\n )\n\n notes_df <- data.frame(\n \"Note number\" = c(\"[note 1]\", \"[note 2]\"),\n \"Note text\" = c(\"Placeholder note.\", \"Placeholder note.\"),\n check.names = FALSE\n )\n\n table_df <- mtcars\n table_df[[\"car [note 1]\"]] <- row.names(mtcars)\n row.names(table_df) <- NULL\n table_df <- table_df[1:5, c(\"car [note 1]\", \"mpg\", \"cyl\")]\n table_df[\"Notes\"] <- c(\"[note 2]\", rep(NA_character_, 4))" + [1] "cover_list <- list(\n \"Section 1\" = c(\"First row of Section 1.\", \"Second row of Section 1.\"),\n \"Section 2\" = \"The only row of Section 2.\",\n \"Section 3\" = c(\n \"[Website](https://co-analysis.github.io/a11ytables/)\",\n \"[Email address](mailto:fake.address@a11ytables.com)\"\n )\n)\n\ncontents_df <- data.frame(\n \"Sheet name\" = c(\"Notes\", \"Table 1\", \"Table 2\"),\n \"Sheet title\" = c(\n \"Notes used in this workbook\",\n \"First Example Sheet\",\n \"Second Example Sheet\"\n ),\n check.names = FALSE\n)\n\nnotes_df <- data.frame(\n \"Note number\" = paste(\"[note \", 1:2, \"]\"),\n \"Note text\" = c(\"First note.\", \"Second note.\"),\n check.names = FALSE\n)\n\ntable_1_df <- data.frame(\n Category = LETTERS[1:10],\n Numeric = 1:10,\n \"Numeric suppressed\" = c(1:4, \"[c]\", 6:9, \"[x]\"),\n \"Numeric thousands\" = abs(round(rnorm(10), 4) * 1e5),\n \"Numeric decimal\" = abs(round(rnorm(10), 5)),\n \"A column with a long name\" = 1:10,\n Notes = c(\"[note 1]\", rep(NA_character_, 4), \"[note 2]\", rep(NA_character_, 4)),\n check.names = FALSE\n)\n\ntable_2_df <- data.frame(Category = LETTERS[1:10], Numeric = 1:10)" diff --git a/tests/testthat/test-a11ytable.R b/tests/testthat/test-a11ytable.R index 91dd9b3..4a641d4 100644 --- a/tests/testthat/test-a11ytable.R +++ b/tests/testthat/test-a11ytable.R @@ -1,16 +1,15 @@ -test_that("a11ytable can be created by hand (with df for cover)", { +test_that("a11ytable can be created by hand (with list for cover)", { - # Uses mtcars_df, which has a data.frame containing cover information in the + # Uses demo_df, which has a list containing cover information in the # 'table' column. x <- suppressWarnings( create_a11ytable( - tab_titles = mtcars_df$tab_title, - sheet_types = mtcars_df$sheet_type, - sheet_titles = mtcars_df$sheet_title, - sources = mtcars_df$source, - tables = mtcars_df$table + tab_titles = demo_df$tab_title, + sheet_types = demo_df$sheet_type, + sheet_titles = demo_df$sheet_title, + tables = demo_df$table ) ) @@ -20,29 +19,27 @@ test_that("a11ytable can be created by hand (with df for cover)", { expect_error( suppressWarnings( create_a11ytable( - tab_titles = mtcars_df$tab_title, + tab_titles = demo_df$tab_title, sheet_types = "x", - sheet_titles = mtcars_df$sheet_title, - sources = mtcars_df$source, - tables = mtcars_df$table + sheet_titles = demo_df$sheet_title, + tables = demo_df$table ) ) ) }) -test_that("a11ytable can be created by hand (with list for cover)", { - - # Uses mtcars_df, which has a list containing cover information in the - # 'table' column. +test_that("a11ytable can be created by hand (with df for cover)", { x <- suppressWarnings( create_a11ytable( - tab_titles = mtcars_df2$tab_title, - sheet_types = mtcars_df2$sheet_type, - sheet_titles = mtcars_df2$sheet_title, - sources = mtcars_df2$source, - tables = mtcars_df2$table + tab_titles = demo_df$tab_title, + sheet_types = demo_df$sheet_type, + sheet_titles = demo_df$sheet_title, + tables = c( + list(data.frame("Section 1" = "Text.", "Section 2" = "Text.")), + demo_df$table[2:length(demo_df$table)] + ) ) ) @@ -52,11 +49,10 @@ test_that("a11ytable can be created by hand (with list for cover)", { expect_error( suppressWarnings( create_a11ytable( - tab_titles = mtcars_df2$tab_title, + tab_titles = demo_df$tab_title, sheet_types = "x", - sheet_titles = mtcars_df2$sheet_title, - sources = mtcars_df2$source, - tables = mtcars_df2$table + sheet_titles = demo_df$sheet_title, + tables = demo_df$table ) ) ) @@ -67,11 +63,10 @@ test_that("strings are not converted to factors", { x <- suppressWarnings( create_a11ytable( - tab_titles = mtcars_df$tab_title, - sheet_types = mtcars_df$sheet_type, - sheet_titles = mtcars_df$sheet_title, - sources = mtcars_df$source, - tables = mtcars_df$table + tab_titles = demo_df$tab_title, + sheet_types = demo_df$sheet_type, + sheet_titles = demo_df$sheet_title, + tables = demo_df$table ) ) @@ -84,7 +79,7 @@ test_that("strings are not converted to factors", { test_that("suitable objects can be coerced", { - x <- suppressWarnings(as_a11ytable(mtcars_df)) + x <- suppressWarnings(as_a11ytable(demo_df)) expect_s3_class(x, class = "a11ytable") expect_identical(class(x), c("a11ytable", "tbl", "data.frame")) @@ -99,45 +94,57 @@ test_that("suitable objects can be coerced", { test_that("class validation works", { - expect_length(suppressWarnings(as_a11ytable(mtcars_df)), 6) + expect_length(suppressWarnings(as_a11ytable(demo_df)), 7) expect_error(as_a11ytable(1)) expect_error(as_a11ytable("x")) expect_error(as_a11ytable(list())) expect_error(as_a11ytable(data.frame())) - x <- mtcars_df + x <- demo_df names(x)[1] <- "foo" expect_error(as_a11ytable(x)) - x <- mtcars_df + x <- demo_df x[["table"]] <- as.character(x[["table"]]) expect_error(as_a11ytable(x)) - x <- mtcars_df[, 1:4] + x <- demo_df[, 1:4] expect_error(as_a11ytable(x)) - x <- mtcars_df[1, ] + x <- demo_df[1, ] expect_error(as_a11ytable(x)) - x <- mtcars_df + x <- demo_df x[x$sheet_type %in% c("cover", "contents"), "sheet_type"] <- "foo" expect_error(as_a11ytable(x)) - x <- mtcars_df + x <- demo_df x[x$tab_title == "Table_2", "sheet_type"] <- "foo" expect_error(as_a11ytable(x)) - x <- mtcars_df + x <- demo_df x$sheet_type <- NA_character_ expect_error(as_a11ytable(x)) - x <- mtcars_df + x <- demo_df + x$custom_rows <- NA_character_ + expect_error(as_a11ytable(x)) + + x <- demo_df + x$custom_rows <- rep(list(1), nrow(x)) + expect_error(as_a11ytable(x)) + + x <- demo_df x[x$tab_title == "Table_2", "tab_title"] <- "Lorem_ipsum_dolor_sit_amet__consectetur_adipiscing" expect_warning(as_a11ytable(x)) - x <- mtcars_df + x <- demo_df + x[x$tab_title == "Table_2", "tab_title"] <- "!?" + expect_warning(as_a11ytable(x)) + + x <- demo_df x[x$sheet_type == "notes", "table"][[1]] <- list( data.frame( @@ -152,14 +159,14 @@ test_that("class validation works", { test_that("summary method works", { - x <- suppressWarnings(as_a11ytable(mtcars_df)) + x <- suppressWarnings(as_a11ytable(demo_df)) expect_output(summary(x)) }) test_that("absence of note sheets doesn't prevent a11ytable formation", { - df <- mtcars_df[mtcars_df$sheet_type != "notes", ] + df <- demo_df[demo_df$sheet_type != "notes", ] suppressWarnings(x <- as_a11ytable(df)) expect_s3_class(x, "a11ytable") @@ -169,17 +176,15 @@ test_that("absence of note sheets doesn't prevent a11ytable formation", { test_that("tab_titles are unique", { - mtcars_df[mtcars_df$tab_title == "Table_2", "tab_title"] <- "Table_1" + demo_df[demo_df$tab_title == "Table_2", "tab_title"] <- "Table_1" expect_error( with( - mtcars_df, + demo_df, create_a11ytable( tab_titles = tab_title, sheet_types = sheet_type, sheet_titles = sheet_title, - blank_cells = blank_cells, - sources = source, tables = table ) ) @@ -193,7 +198,7 @@ test_that("tbl output looks as intended", { tab_titles = LETTERS[1:3], sheet_type = c("cover", "contents", "tables"), sheet_titles = LETTERS[1:3], - sources = c(rep(NA_character_, 2), "x."), + sources = c(NA_character_, NA_character_, "Source"), tables = list( data.frame(x = "x"), data.frame(tab = "x", title = "x"), @@ -209,13 +214,13 @@ test_that("input other than data.frame is intercepted during validation", { expect_error(.validate_a11ytable("x")) - x <- mtcars_df; x[, "table"] <- "x" + x <- demo_df; x[, "table"] <- "x" expect_error( as_a11ytable(x), "Column 'table' must be a listcol of data.frame objects." ) - y <- subset(mtcars_df, select = -table) + y <- subset(demo_df, select = -table) y[, "table"] <- list(rep(list("x"), nrow(y))) expect_error( as_a11ytable(y), @@ -227,11 +232,11 @@ test_that("input other than data.frame is intercepted during validation", { test_that("only one cover, contents, notes can be used", { cover_dupe <- - rbind(mtcars_df, mtcars_df[mtcars_df$sheet_type == "cover", ]) + rbind(demo_df, demo_df[demo_df$sheet_type == "cover", ]) contents_dupe <- - rbind(mtcars_df, mtcars_df[mtcars_df$sheet_type == "contents", ]) + rbind(demo_df, demo_df[demo_df$sheet_type == "contents", ]) notes_dupe <- - rbind(mtcars_df, mtcars_df[mtcars_df$sheet_type == "notes", ]) + rbind(demo_df, demo_df[demo_df$sheet_type == "notes", ]) expect_error(as_a11ytable(cover_dupe)) expect_error(as_a11ytable(contents_dupe)) @@ -241,10 +246,10 @@ test_that("only one cover, contents, notes can be used", { test_that("NAs in certain columns cause failure", { - tab_na <- mtcars_df; tab_na$tab_title <- NA_character_ - type_na <- mtcars_df; type_na$sheet_type <- NA_character_ - title_na <- mtcars_df; title_na$sheet_title <- NA_character_ - table_na <- mtcars_df; table_na$table <- NA_character_ + tab_na <- demo_df; tab_na$tab_title <- NA_character_ + type_na <- demo_df; type_na$sheet_type <- NA_character_ + title_na <- demo_df; title_na$sheet_title <- NA_character_ + table_na <- demo_df; table_na$table <- NA_character_ expect_error(as_a11ytable(tab_na)) expect_error(as_a11ytable(type_na)) @@ -255,27 +260,18 @@ test_that("NAs in certain columns cause failure", { test_that("Note mismatch is caught", { - x <- mtcars_df[!mtcars_df$tab_title == "Table_1", ] + x <- demo_df[!demo_df$tab_title == "Table_1", ] x[x$sheet_type == "contents", "table"][[1]] <- list(data.frame(x = c("x", "y"), y = c("x", "y"))) expect_warning(as_a11ytable(x), "You have a 'notes' sheet") - # y <- mtcars_df[!mtcars_df$tab_title == "Table_2", ] - # y[y$sheet_type == "contents", "table"][[1]] <- - # list(data.frame(x = c("x", "y"), y = c("x", "y"))) - # table_extra_note <- y[y$tab_title == "Table_1", "table"][[1]] - # names(table_extra_note)[1] <- "Car [note 3]" - # y[y$tab_title == "Table_1", "table"][[1]] <- list(table_extra_note) - # - # expect_warning(as_a11ytable(y), "Some notes are in the tables") - - z <- mtcars_df[!mtcars_df$tab_title == "Table_2", ] + z <- demo_df[!demo_df$tab_title == "Table_2", ] z[z$sheet_type == "contents", "table"][[1]] <- list(data.frame(x = c("x", "y"), y = c("x", "y"))) z[z$sheet_type == "notes", "table"][[1]] <- list( data.frame( - `Note number` = paste0("[note ", 1:3, "]"), + `Note number` = paste0("[note ", 1:4, "]"), `Note text` = "x", check.names = FALSE ) @@ -288,9 +284,9 @@ test_that("Note mismatch is caught", { test_that("warning is raised if a source statement is missing", { - mtcars_df[mtcars_df$tab_title == "Table_1", "source"] <- NA_character_ + demo_df[demo_df$tab_title == "Table_1", "source"] <- NA_character_ expect_warning( - as_a11ytable(mtcars_df), + as_a11ytable(demo_df), "One of your tables is missing a source statement." ) @@ -298,9 +294,9 @@ test_that("warning is raised if a source statement is missing", { test_that("warning is raised if there's no blank cells but there is a reason", { - mtcars_df[mtcars_df$tab_title == "Table_2", "blank_cells"] <- "x" + demo_df[demo_df$tab_title == "Table_2", "blank_cells"] <- "x" expect_warning( - as_a11ytable(mtcars_df), + as_a11ytable(demo_df), "There's no blank cells in these tables" ) @@ -329,11 +325,11 @@ test_that("tab titles are cleaned and warnings provided", { expect_warning(.clean_tab_titles("Table!@£#$%^&*(){}[]-=+;:'\"\\|<>,.~`/?1")) expect_warning(.clean_tab_titles(long_title)) - x <- mtcars_df + x <- demo_df x[1, "tab_title"] <- long_title expect_warning(as_a11ytable(x)) - y <- mtcars_df + y <- demo_df y[1, "tab_title"] <- "Cover!" expect_warning(as_a11ytable(y)) @@ -341,14 +337,14 @@ test_that("tab titles are cleaned and warnings provided", { test_that("input column names are okay", { - names(mtcars_df)[1] <- "x" - expect_error(as_a11ytable(mtcars_df)) + names(demo_df)[1] <- "x" + expect_error(as_a11ytable(demo_df)) }) test_that("character class columns are caught if not character class", { - mtcars_df[, "sheet_type"] <- 1:nrow(mtcars_df) - expect_error(as_a11ytable(mtcars_df)) + demo_df[, "sheet_type"] <- 1:nrow(demo_df) + expect_error(as_a11ytable(demo_df)) }) diff --git a/tests/testthat/test-addins.R b/tests/testthat/test-addins.R index 7ae6112..23878c5 100644 --- a/tests/testthat/test-addins.R +++ b/tests/testthat/test-addins.R @@ -4,9 +4,5 @@ test_that("string_create_a11ytable skeleton is okay", { }) test_that("string_tables_tibble skeleton is okay", { - expect_snapshot_output(string_tables_tibble()) -}) - -test_that("string_tables_df skeleton is okay", { - expect_snapshot_output(string_tables_df()) + expect_snapshot_output(string_tables()) }) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R new file mode 100644 index 0000000..52ebf07 --- /dev/null +++ b/tests/testthat/test-utils.R @@ -0,0 +1,5 @@ +test_that("vector elements are converted to sentence form", { + expect_equal(.vector_to_sentence(LETTERS[1]), "A") + expect_equal(.vector_to_sentence(LETTERS[1:2]), "A and B") + expect_equal(.vector_to_sentence(LETTERS[1:3]), "A, B and C") +}) diff --git a/tests/testthat/test-workbook.R b/tests/testthat/test-workbook.R index 24cd96b..52e37a4 100644 --- a/tests/testthat/test-workbook.R +++ b/tests/testthat/test-workbook.R @@ -1,6 +1,6 @@ test_that("workbook object is created", { - x <- suppressWarnings(generate_workbook(as_a11ytable(mtcars_df))) + x <- suppressWarnings(generate_workbook(as_a11ytable(demo_df))) expect_s4_class(x, class = "Workbook") expect_identical(class(x)[1], "Workbook") @@ -9,7 +9,7 @@ test_that("workbook object is created", { test_that("a11ytable is passed", { - x <- suppressWarnings(as_a11ytable(mtcars_df)) + x <- suppressWarnings(as_a11ytable(demo_df)) expect_error(generate_workbook("x")) expect_error(generate_workbook(1)) @@ -21,7 +21,7 @@ test_that("a11ytable is passed", { test_that(".stop_bad_input works as intended", { wb <- openxlsx::createWorkbook() - a11ytable <- as_a11ytable(mtcars_df) + a11ytable <- as_a11ytable(demo_df) expect_error(.stop_bad_input("x", a11ytable, "cover")) expect_error(.stop_bad_input(wb, a11ytable, 1)) @@ -30,12 +30,8 @@ test_that(".stop_bad_input works as intended", { test_that("hyperlinks are generated on the cover page", { - # mtcars_df demo dataset does not contain any hyperlinks on the cover - x <- suppressWarnings(generate_workbook(as_a11ytable(mtcars_df))) - expect_length(x$worksheets[[1]]$hyperlinks, 0) - - # mtcars_df2 demo dataset has two hyperlinks on the cover - y <- suppressWarnings(generate_workbook(as_a11ytable(mtcars_df2))) + # demo dataset has two hyperlinks on the cover + y <- suppressWarnings(generate_workbook(as_a11ytable(demo_df))) expect_length(y$worksheets[[1]]$hyperlinks, 2) }) diff --git a/vignettes/a11ytables.Rmd b/vignettes/a11ytables.Rmd index 07708be..d1bead0 100644 --- a/vignettes/a11ytables.Rmd +++ b/vignettes/a11ytables.Rmd @@ -37,24 +37,23 @@ Having installed the {a11ytables} package, there are three steps to generating a 1. Pass the output to `generate_workbook()` to convert the a11ytable to {openxlsx}'s 'Workbook' class, which adds spreadsheet structure and styles 1. Pass the output to `openxlsx::saveWorkbook()` to write out to an xlsx file (or `openxlsx::openXL()` to open a temporary copy) -The minimal demo below follows these steps using the example data from [the classic mtcars dataset](https://rdrr.io/r/datasets/mtcars.html). - You can use the package's [RStudio Addin](https://rstudio.github.io/rstudioaddins/), which is installed with {a11ytables}, to insert a pre-filled demo skeleton of this workflow (RStudio users only). ### 1. Create an a11ytable Each argument to `create_a11ytable()` provides the information needed to construct each sheet in the spreadsheet. -| Argument | Type | Required | Accepted values | Explanation | -| :--- | :--- | :- | :--- | :------ | -| `tab_titles` | Character vector | Yes | | The name that will appear on each sheet's tab in the output spreadsheet | -| `sheet_types` | Character vector | Yes | 'contents', 'cover', 'notes', 'tables' | The kind of information that the sheet holds, which is needed so that the correct structure and formatting can be applied later | -| `sheet_titles` | Character vector | Yes | | The main heading of each sheet, which will appear in cell A1 | -| `blank_cells` | Character vector | No | | A sentence that explains the reason for any blank cells in the sheet (if applicable) | -| `sources` | Character vector | No | | A sentence provides the source of the data found in each table (if applicable, likely only needed for sheets with `sheet_types` of 'table') | -| `tables` | List of dataframes (cover can be a list) | Yes | | The main content for each sheet, expressed as flat ([probably tidy](https://www.jstatsoft.org/article/view/v059i10)) dataframes of rows and columns (though the cover can be a list) | +| Argument | Required | Type | Accepted values | Explanation | +| :--- | :- | :--- | :--- | :------ | +| `tab_titles` | Yes | Character vector | | The name that will appear on each sheet's tab in the output spreadsheet | +| `sheet_types` | Yes | Character vector | 'cover', 'contents', 'notes', 'tables' | The kind of information that the sheet holds, which is needed so that the correct structure and formatting can be applied later | +| `sheet_titles` | Yes | Character vector | | The main heading of each sheet, which will appear in cell A1 | +| `blank_cells` | No | Character vector | | A sentence that explains the reason for any blank cells in the sheet (if applicable) | +| `custom_rows` | No | List of character vectors | | Arbitrary rows of text that the user wants to insert above a table, one list-item per sheet (contents, notes and tables sheets), one vector element per row | +| `sources` | No | Character vector | | A sentence provides the source of the data found in each table (if applicable, likely only needed for sheets with `sheet_types` of 'table') | +| `tables` | Yes | List of dataframes (although the cover sheet content can be provided as a list object) | | The main content for each sheet, expressed as flat ([probably tidy](https://www.jstatsoft.org/article/view/v059i10)) dataframes of rows and columns (though the cover can be a list) | -You can read more about these arguments and their requirements in the function's help pages, which you can access by running `?create_a11ytable`. See also the [terminology vignette](https://co-analysis.github.io/a11ytables/articles/terminology), `vignette("terminology", "a11ytables")`, for these terms and more. +You can read more about these arguments and their requirements in the function's help pages, which you can access by running `?create_a11ytable` in the R console. See also the [terminology vignette](https://co-analysis.github.io/a11ytables/articles/terminology), `vignette("terminology", "a11ytables")`, for these terms and more. #### Pre-prepare tables @@ -69,55 +68,43 @@ Note that you can use the RStudio Addin 'Insert table templates using 'tibble'' The cover can accept either a list or a data.frane (the latter was the only acceptable input prior to version 0.2.0). We recommend a list so that you can have multiple rows per section on the cover. This also means you can dedicate certain rows to be hyperlinks to web URLs or mailto links that will open an email client. Here's a demo list for the contents page (required): ```{r tables-cover-list} -cover_list <- list( - "Description" = "Aspects of automobile design and performance.", - "Properties" = "Suppressed values are replaced with the value '[c]'.", - "Contact" = c( - "Contact the mtcars Team.", - "Telephone 0123456789.", - "Email us at [invented_email@not_real.com](mailto:invented_email@not_real.com)." +cover_list <- list( + "Section 1" = c("First row of Section 1.", "Second row of Section 1."), + "Section 2" = "The only row of Section 2.", + "Section 3" = c( + "[Website](https://co-analysis.github.io/a11ytables/)", + "[Email address](mailto:fake.address@a11ytables.com)" ) ) ``` -
Click for an alternative data.frame input for the cover. - -Instead of a list, you could provide a data.frame that contains information for the cover sheet. The limitation with this approach is that---for each section on the cover sheet---you are restricted to one cell for the header and one cell for body content. That might not be ideal if you want to dedicate some cells within a section to be hyperlinks. - -Here's a demo data.frame for the cover that has a column for sub-headings and a column for the content of each one: - -```{r tables-cover-df, eval=FALSE} -cover_df <- tibble::tribble( - ~"subsection_title", ~"subsection_content", - "Description", "Aspects of automobile design and performance.", - "Properties", "Suppressed values are replaced with the value '[c]'.", - "Contact", "The mtcars Team, telephone 0123456789." -) -``` - -
+Note: a list is the preferred method of input for the cover. Previously, a data.frame was the only way to supply the data for the cover sheet in version 0.1 of the package. Here's a demo table for the contents page (required): ```{r tables-contents} -contents_df <- tibble::tribble( - ~"Sheet name", ~"Sheet title", - "Notes", "Notes used in this workbook", - "Table 1", "Car Road Tests (demo)" +contents_df <- data.frame( + "Sheet name" = c("Notes", "Table_1", "Table_2"), + "Sheet title" = c( + "Notes used in this workbook", + "First Example Sheet", + "Second Example Sheet" + ), + check.names = FALSE ) ``` -And here's a demo table for the notes page (not required if there's no notes in your tables), which has a column for the note number in the form '[note x]' and and column for the note itself: +And here's a demo table for the notes page (not required if there's no notes in your tables), which has a column for the note number in the form '[note x]' and a column for the note itself: ```{r tables-notes} -notes_df <- tibble::tribble( - ~"Note number", ~"Note text", - "[note 1]", "US gallons.", - "[note 2]", "Retained to enable comparisons with previous analyses." +notes_df <- data.frame( + "Note number" = paste0("[note ", 1:3, "]"), + "Note text" = c("First note.", "Second note.", "Third note."), + check.names = FALSE ) ``` -
Click to preview these tables +
Click to preview these objects ```{r tables-meta-expand} cover_list contents_df @@ -127,31 +114,31 @@ notes_df ##### Statistical tables -This code generates a demo dataframe that we're going to pretend is the statistical data that we want to publish: - -```{r} -stats_df <- mtcars |> - head() |> - tibble::rownames_to_column("car") |> - subset(select = c("car", "cyl", "mpg")) - -names(stats_df) <- c( - "Car", - "Cylinder count", - "Miles per gallon [note 1]" # notes go in headers, not cells +The code below generates a demo data.frame that we're going to pretend is the statistical data that we want to publish. It has columns with different sorts of data that we might want to publish. It also has suppressed values (e.g. '[c]' meaning 'confidential' data) and includes notes (in the form '[note x]'). + +```{r stats-df-1} +table_1_df <- data.frame( + Category = LETTERS[1:10], + "Numeric [note 1]" = 1:10, + "Numeric suppressed" = c(1:4, "[c]", 6:9, "[x]"), + "Numeric thousands" = abs(round(rnorm(10), 4) * 1e5), + "Numeric decimal" = abs(round(rnorm(10), 5)), + "This column has a very long name that means that the column width needs to be widened" = 1:10, + Notes = c("[note 1]", rep(NA_character_, 4), "[note 2]", rep(NA_character_, 4)), + check.names = FALSE ) +``` -stats_df$Notes <- c( # add 'Notes' column - rep("[note 2]", 2), - rep(NA_character_, 4) -) +We'll create a second, simpler table as well, which will go on a separate sheet: -stats_df[3, 2:3] <- "[c]" # suppressed (confidential) data +```{r stats-df-2} +table_2_df <- data.frame(Category = LETTERS[1:10], Numeric = 1:10) ``` -
Click to preview this table +
Click to preview these tables of statistical data ```{r table-stat-expand} -stats_df +table_1_df +table_2_df ```
@@ -159,53 +146,51 @@ See [the best practice guidance](https://analysisfunction.civilservice.gov.uk/po #### Create a11ytable -Now we can construct an a11ytable by passing the required sheet elements as character vectors with `c()`, or a `list()` in the case of the `tables` argument, to the `create_a11ytable()` function. You can use `NA_character_` wherever an element isn't required. +Now we can construct an a11ytable by passing the required sheet elements as character vectors with `c()`—or a `list()` in the case of the `tables` and `custom_rows` arguments—to the `create_a11ytable()` function. + +Note that: -Note that you can use the RStudio Addin 'Insert 'a11ytable' Template' to insert a demo skeleton into your R script. +* the element index of the object supplied to each argument is the sheet that it will be applied to (e.g. the tab title of the first sheet will be 'Cover', the sheet type of the second sheet will be 'contents' and the fourth sheet will contain the `table_1_df` table) +* you must use `NA_character_` wherever an element isn't required (e.g. there is no information about blank cells nor sources for the first three sheets) +* you can insert a template of this demo using the package's RStudio Addin ```{r new-a11ytable} my_a11ytable <- a11ytables::create_a11ytable( - tab_titles = c( - "Cover", - "Contents", - "Notes", - "Table 1" - ), - sheet_types = c( - "cover", - "contents", - "notes", - "tables" - ), + tab_titles = c("Cover", "Contents", "Notes", "Table 1", "Table_2"), + sheet_types = c("cover", "contents", "notes", "tables", "tables"), sheet_titles = c( - "The 'mtcars' Demo Datset", + "The 'a11ytables' Demo Workbook", "Table of contents", "Notes", - "Table 1: Car Road Tests" + "Table 1: First Example Sheet", + "Table 2: Second Example Sheet" ), blank_cells = c( + rep(NA_character_, 3), + "Blank cells indicate that there's no note in that row.", + NA_character_ + ), + custom_rows = list( NA_character_, NA_character_, - NA_character_, - "Blank cells indicate that there's no note in that row" + "A custom row.", + c( + "First custom row [with a hyperlink.](https://co-analysis.github.io/a11ytables/)", + "Second custom row." + ), + "A custom row." ), sources = c( - NA_character_, - NA_character_, - NA_character_, - "Motor Trend (1974)" + rep(NA_character_, 3), + "[The Source Material., 2024](https://co-analysis.github.io/a11ytables/)", + "The Source Material, 2024." ), - tables = list( - cover_list, - contents_df, - notes_df, - stats_df - ) + tables = list(cover_list, contents_df, notes_df, table_1_df, table_2_df) ) ``` -The function will return errors or warnings if anything is missing or seems odd. For example, we were warned that a value we supplied to `tab_title` had to be cleaned from 'Table 1' to 'Table_1', since blank spaces are not allowed. +The function will return errors or warnings if anything is missing or seems odd. For example, we were warned that a value we supplied to `tab_title` had to be cleaned from 'Table 1' to 'Table_1', since blank spaces are not allowed in tab names. Here's a preview of the object that was created: @@ -217,7 +202,7 @@ You can immediately tell that this is an a11ytable because it's the first word t So our a11ytable is basically just a table with one row per sheet and one column per sheet element. In fact, it has class 'data.frame'/'tbl' along with 'a11ytable'. For convenience, you can also check for the a11ytable class with `is_a11ytable()`. -Note that `create_a11ytable()` is the preferred method for generating a11ytable-class objects, but it's also possible to convert a correctly-formatted, pre-built data.frame or tibble directly to an a11ytable with `as_a11ytable()`. You can try this using the `mtcars_df` data.frame that's provided with {a11ytables}, which is a specially-prepared subset of [the mtcars dataset](https://rdrr.io/r/datasets/mtcars.html). +Note that `create_a11ytable()` is the preferred method for generating a11ytable-class objects, but it's also possible to convert a correctly-formatted, pre-built data.frame or tibble directly to an a11ytable with `as_a11ytable()`. ### 2. Convert to a workbook @@ -250,7 +235,7 @@ You could also open a temporary copy of the workbook with `openxlsx::openXL()`, #### Output -The content of the output spreadsheet will look like this: +The content of your output spreadsheet will end up looking something like this:
Gif of a simple Excel workbook created using the a11ytables package, revealing tabs for cover, contents, notes and a table. @@ -258,23 +243,22 @@ The content of the output spreadsheet will look like this: You'll notice that various best-practice formatting (e.g. Arial size 12 font for body text) and mark-up (e.g. tables, donated by a marker in the lower-right corner of the lower-right cell of each one) have been applied throughout. -Note also that two 'pre-table' meta-elements were created automatically in the sheets that contain statistical tables, which you didn't need to supply to `create_a11ytable()`: (1) the number of tables and (2) the presence of notes. +Note also that two 'pre-table' meta-elements were created automatically in the sheets that contain statistical tables, which you didn't need to supply to `create_a11ytable()`: (1) the number of tables and (2) the presence of notes. These are required for accessibility good practice. #### Final tweaks -It's your responsibility to check and amend the output from {a11ytables} to ensure it meets user's accessibility needs. +It's your responsibility to check and amend the output from {a11ytables} to ensure it meets users' accessibility needs. You can apply some final tweaks to the output xlsx file if the defaults don't quite meet your requirements (e.g. some column widths), though it's advisable to keep changes to a minimum for reproducibility purposes and because you may undo some of the compliant structuring and formatting that {a11ytables} provides. -At time of writing (v0.2.0) you might want to address manually some other [accessibility requirements](https://analysisfunction.civilservice.gov.uk/policy-store/releasing-statistics-in-spreadsheets/) that are not yet covered by the package: +At time of writing (v0.3.0) you might want to address manually some other [accessibility requirements](https://analysisfunction.civilservice.gov.uk/policy-store/releasing-statistics-in-spreadsheets/) that are not yet covered by the package: 1. Fill in the document properties, which you are likely to find under 'File' then 'Properties' in your spreadsheet software -2. Convert text to hyperlinks, where required 3. Convert to an ODS file rather than the proprietary xlsx format 4. Various number-formatting issues, like padding decimal places with zeroes -Please [see the issues on GitHub](https://github.com/co-analysis/a11ytables/issues); we're trying to address a number of these limitations. +We're trying to address a number of these limitations. Please [see the issues on GitHub](https://github.com/co-analysis/a11ytables/issues) for the current status. -# Contribute +## Contribute To contribute, please add [an issue](https://github.com/co-analysis/a11ytables/issues) or [a pull request](https://github.com/co-analysis/a11ytables/pulls) after reading [the code of conduct](https://github.com/co-analysis/a11ytables/blob/main/CODE_OF_CONDUCT.md) and [contributing](https://github.com/co-analysis/a11ytables/blob/main/.github/CONTRIBUTING.md) guidance. diff --git a/vignettes/checklist.Rmd b/vignettes/checklist.Rmd index 0f2c616..702d771 100644 --- a/vignettes/checklist.Rmd +++ b/vignettes/checklist.Rmd @@ -44,6 +44,7 @@ There are several self-assessed indicators used in the 'status' column of the ta | Avoid adding filters and freeze panes | No | Met | Filters and freezing aren't supported by {a11ytables} | | Only leave cells with no data empty in certain circumstances | Yes | User's responsibility | The user should describe why there are blank cells in the `blank_cells` column of their a11ytables object | | Avoid hiding rows or columns | No | Met | Hiding isn't supported by {a11ytables} | +| Columns should be a sensible width | No | Partly | {a11ytables} uses a fixed default column width for data tables (16), which is doubled (32) if the length of the content exceeds a threshold number of characters (50) | ### Footnotes @@ -58,7 +59,7 @@ There are several self-assessed indicators used in the 'status' column of the ta | Description | Essential? | Status | Explanation | | :-- | :- | :- | :---- | -| All written content needs to meet the accessibility guidelines | | User's responsibility | {a11ytables} does not check the validity of user-supplied text | +| All written content needs to meet the accessibility guidelines | No | User's responsibility | {a11ytables} does not check the validity of user-supplied text | | Links must be accessible | Yes | User's responsibility | {a11ytables} does not check the validity of user-supplied text | | Format text to make it accessible | No | Met | `generate_workbook()` auto-formats the text | | All worksheets should have descriptive titles which are properly tagged and formatted | Yes | Partly/user’s responsibility | {a11ytables} does not check user-supplied text nor data; the package handles formatting but not yet mark-up for headings | diff --git a/vignettes/structure.Rmd b/vignettes/structure.Rmd index 81460af..8b4c113 100644 --- a/vignettes/structure.Rmd +++ b/vignettes/structure.Rmd @@ -1,5 +1,5 @@ --- -title: "Package structure (advanced)" +title: "Package structure" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{structure} @@ -23,7 +23,9 @@ This vignette is aimed at developers who want to understand the package better a There are only two main user-facing functions in {a11ytables}: * `create_a11ytable()` to create a data.frame object (with an additional 'a11ytable' S3 class) filled with all the information needed to create a spreadsheet output, as well as check the validity of the structure and provide errors or warnings -* ` generate_workbook()` to convert the output from `create_a11ytable()` to an [{openxlsx}](https://ycphs.github.io/openxlsx/) Workbook-class object, ready for writing to an xlsx file with `openxlsx::saveWorkbook()` +* `generate_workbook()` to convert the output from `create_a11ytable()` to an [{openxlsx}](https://ycphs.github.io/openxlsx/) Workbook-class object, ready for writing to an xlsx file with `openxlsx::saveWorkbook()` + +This simplicity is a feature, not a bug. It's designed to greatly simplify the process of creating compliant spreadsheets. The package does the hard work of making the outputs compliant so the user spends less time dealing with it. This vignette provides a quick look at what's happening 'under the hood' in these functions. @@ -56,7 +58,7 @@ Basically, `as_a11ytable()` creates an S3-class object with classes 'data.frame' ```{r class-demo} library(a11ytables) -my_a11ytable <- as_a11ytable(mtcars_df) +my_a11ytable <- as_a11ytable(demo_df) class(my_a11ytable) ``` @@ -77,7 +79,7 @@ Within `as_a11ytable()` itself are two major functions that help ensure proper c * `.validate_a11ytable()` will generate errors if basic structural expectations of an a11ytable aren't met (e.g. if 'cover', 'contents' or 'notes' have been provided more than once to the `sheet_type` argument) * `.warn_a11ytable()` checks for things that the user may have forgotten and prints warnings about them (e.g. if 5 notes are declared in the notes sheet but there are fewer in the tables themselves) -Advanced users can create a correctly-formatted data.frame on the fly and convert it to an a11ytable with `as_a11ytable()` directly. The `as_a11ytable()` function mainly exists to make testing easier, i.e. you can pass to the pre-prepared `mtcars_df` dataset. +Advanced users can create a correctly-formatted data.frame on the fly and convert it to an a11ytable with `as_a11ytable()` directly. The `as_a11ytable()` function mainly exists to make testing easier, i.e. you can pass to it the pre-prepared `demo_df` dataset. ### Methods @@ -89,7 +91,7 @@ There's a few methods for a11ytables that are also found in `R/a11ytables.R`. is_a11ytable(my_a11ytable) ``` -The `summary()` method prints a very simple 'long' overview of a provided a11ytable. +The `summary()` method prints a very simple overview of a provided a11ytable. ```{r summary-demo} summary(my_a11ytable) @@ -144,6 +146,7 @@ The following functions insert 'pre-table' elements in this order: * `.insert_table_count()` to add a statement about the number of tables in the sheet * `.insert_notes_statement()` if a `sheet_type` of 'notes' is provided in the user's a11ytable * `.insert_blanks_message()` if content is provided in the `blanks_cells` column of the user's a11ytable +* `.insert_custom_rows()` if content is provided in the `custom_rows` column of the user's a11ytable * `.insert_source()` if content is provided in the `source` column of the user's a11ytable A table of data is added under the metadata with `.insert_table()`, which is provided in the `table` column of the user's a11ytable object. @@ -151,7 +154,7 @@ A table of data is added under the metadata with `.insert_table()`, which is pro The exact `.insert_*()` functions called depend on the `sheet_type` declared in the a11ytable: * meta sheets (cover, contents and notes) need only `.insert_title()` and `.insert_table_count()` -* statistical tables also require `.insert_source()` and `.insert_blanks_message()` if provided, as well as `.insert_notes_statement()` if there are notes +* statistical tables may also require `.insert_blanks_message()`, `.insert_custom_rows()` and `.insert_source()` if the relevant content is provided by the user, as well as `.insert_notes_statement()` if there are notes Simple logic is used to check for the presence of meta elements with the `.has_*()` functions, while the `get_start_row_*()` functions handle the cell to which each message should be inserted.