From db8e2ce16595e8f8be594e1811f4e6227b9f84d5 Mon Sep 17 00:00:00 2001 From: dermirko Date: Tue, 16 Apr 2024 22:14:41 +0200 Subject: [PATCH 1/2] implementation of additional support for column groups in interactive tables #1618 --- R/render_as_i_html.R | 55 +++++++- R/tab_create_modify.R | 4 + inst/css/gt_styles_default.scss | 5 + man/tab_spanner.Rd | 6 +- tests/testthat/_snaps/tab_spanner.md | 10 ++ tests/testthat/test-tab_spanner.R | 189 +++++++++++++++++++++++++++ 6 files changed, 264 insertions(+), 5 deletions(-) create mode 100644 tests/testthat/_snaps/tab_spanner.md diff --git a/R/render_as_i_html.R b/R/render_as_i_html.R index 9b7444401e..1c0e189453 100644 --- a/R/render_as_i_html.R +++ b/R/render_as_i_html.R @@ -50,6 +50,9 @@ render_as_ihtml <- function(data, id) { # Determine if the rendered table should have a header section has_header_section <- dt_heading_has_title(data = data) + # Determine if there are tab spanners + has_tab_spanners <- dt_spanners_exists(data = data) + # Obtain the language from the `locale`, if provided locale <- dt_locale_get_value(data = data) @@ -340,6 +343,48 @@ render_as_ihtml <- function(data, id) { footer_component <- NULL } + # Generate the column groups, if there are any tab_spanners + colgroups_def <- NULL + + if (has_tab_spanners) { + col_groups <- (dt_spanners_get(data = data) %>% dplyr::filter(spanner_level == 1)) + + if (max(dt_spanners_get(data = data)$spanner_level) > 1) { + first_colgroups <- base::paste0(col_groups$built, collapse = "|") + + cli::cli_warn(c( + "When displaying an interactive gt table, there must not be more than 1 level of column groups (tab_spanners)", + "*" = "Currently there are {max(dt_spanners_get(data = data)$spanner_level)} levels of tab spanners.", + "i" = "Only the first level will be used for the interactive table, that is: [{first_colgroups}]" + )) + } + + colgroups_def <- + apply( + col_groups, 1, + FUN = function(x) { + reactable::colGroup( + name = x$spanner_label, + columns = x$vars, + header = x$built, + html = TRUE, + align = NULL, + headerVAlign = NULL, + sticky = NULL, + headerClass = NULL, + headerStyle = list( + fontWeight = "normal", + borderBottomStyle = column_labels_border_bottom_style, + borderBottomWidth = column_labels_border_bottom_width, + borderBottomColor = column_labels_border_bottom_color, + marginLeft = "4px", + marginRight = "4px" + ) + ) + } + ) + } + # Generate the default theme for the table tbl_theme <- reactable::reactableTheme( @@ -353,15 +398,17 @@ render_as_ihtml <- function(data, id) { style = list( fontFamily = font_family_str ), - tableStyle = NULL, - headerStyle = list( + tableStyle = list( borderTopStyle = column_labels_border_top_style, borderTopWidth = column_labels_border_top_width, - borderTopColor = column_labels_border_top_color, + borderTopColor = column_labels_border_top_color + ), + headerStyle = list( borderBottomStyle = column_labels_border_bottom_style, borderBottomWidth = column_labels_border_bottom_width, borderBottomColor = column_labels_border_bottom_color ), + # individually defined for the margins left+right groupHeaderStyle = NULL, tableBodyStyle = NULL, rowGroupStyle = NULL, @@ -387,7 +434,7 @@ render_as_ihtml <- function(data, id) { reactable::reactable( data = data_tbl, columns = col_defs, - columnGroups = NULL, + columnGroups = colgroups_def, rownames = NULL, groupBy = NULL, sortable = use_sorting, diff --git a/R/tab_create_modify.R b/R/tab_create_modify.R index af80be79c9..5c3c887aae 100644 --- a/R/tab_create_modify.R +++ b/R/tab_create_modify.R @@ -214,6 +214,10 @@ tab_header <- function( #' and `spanners`, placing the spanner label where it will fit. The first #' spanner level (right above the column labels) is `1`. #' +#' In combination with [opt_interactive()] or `ihtml.active = TRUE` in +#' [tab_options()] only level `1` is supported, additional levels would be +#' discarded. +#' #' @param id *Spanner ID* #' #' `scalar` // *default:* `label` diff --git a/inst/css/gt_styles_default.scss b/inst/css/gt_styles_default.scss index 92e7431d98..af744b2750 100644 --- a/inst/css/gt_styles_default.scss +++ b/inst/css/gt_styles_default.scss @@ -435,4 +435,9 @@ display: inline-flex !important; margin-bottom: 0.75em !important; } + + div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { + height: 0px !important; + } + } diff --git a/man/tab_spanner.Rd b/man/tab_spanner.Rd index 6107d02a10..d14dd915ff 100644 --- a/man/tab_spanner.Rd +++ b/man/tab_spanner.Rd @@ -57,7 +57,11 @@ argument works in tandem with the \code{columns} argument.} An explicit level to which the spanner should be placed. If not provided, \strong{gt} will choose the level based on the inputs provided within \code{columns} and \code{spanners}, placing the spanner label where it will fit. The first -spanner level (right above the column labels) is \code{1}.} +spanner level (right above the column labels) is \code{1}. + +In combination with \code{\link[=opt_interactive]{opt_interactive()}} or \code{ihtml.active = TRUE} in +\code{\link[=tab_options]{tab_options()}} only level \code{1} is supported, additional levels would be +discarded.} \item{id}{\emph{Spanner ID} diff --git a/tests/testthat/_snaps/tab_spanner.md b/tests/testthat/_snaps/tab_spanner.md new file mode 100644 index 0000000000..9469413b04 --- /dev/null +++ b/tests/testthat/_snaps/tab_spanner.md @@ -0,0 +1,10 @@ +# multiple levels of `tab_spanner()` are not compatible with interactive tables [plain] + + Code + local({ + tmp <- exibble[, 1:4] %>% gt() %>% tab_spanner(label = "spanner_numdat", + columns = c(num, date)) %>% tab_spanner(label = "spanner_char", columns = c( + char)) %>% tab_spanner(label = "spanner_numdatchar", columns = c(num, date, + char)) %>% opt_interactive() + }) + diff --git a/tests/testthat/test-tab_spanner.R b/tests/testthat/test-tab_spanner.R index 3ac719bd08..0884e30246 100644 --- a/tests/testthat/test-tab_spanner.R +++ b/tests/testthat/test-tab_spanner.R @@ -15,6 +15,18 @@ selection_text <- function(html, selection) { rvest::html_text(rvest::html_nodes(html, selection)) } +# returns the json-object of the reactable javascript-part +reactive_table_to_json <- function(reactable_obj){ + tmp_html_tag <- reactable_obj %>% + htmltools::as.tags() %>% + htmltools::doRenderTags() %>% + stringr::str_match(pattern = '