Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Respect tab_stubhead() in opt_interactive()+ fix rendering for missing row names #1758

Merged
merged 4 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

* `opt_interactive()` now shows row names if `rownames_to_stub = TRUE` (@olivroy, #1702).

* `opt_ineractive()` now displays the stub header label created with `tab_stubhead()` (@olivroy, #1758).

* `data_color()` throws a more informative error message if `rows` didn't resolve to anything (@olivroy, #1659).

* PDF output now allows the font size of a table to be set using the table.font.size parameter in the tab_options function (#1472). The font sizes of individual table cells (including those in the body, stubs, column headings, etc.) can be set using tab_style function. Several other options specified in tab_style are now reflected in PDF output.
Expand Down
165 changes: 103 additions & 62 deletions R/render_as_i_html.R
Original file line number Diff line number Diff line change
Expand Up @@ -55,94 +55,80 @@ render_as_ihtml <- function(data, id) {

# Obtain the language from the `locale`, if provided
locale <- normalize_locale(dt_locale_get_value(data = data))

# Generate a `lang_defs` object to pass to the `language` argument
if (is.null(locale) || locale == "en") {

lang_defs <- reactable::reactableLang()
lang_defs <- get_ihtml_translations(locale)

column_groups <- dt_boxhead_get_vars_groups(data = data)
# if dt_boxhead_get_vars_groups is fixed, this will no longer be necessary.
if (identical(column_groups, NA_character_)) {
column_groups <- NULL
}
rownames_to_stub <- stub_rownames_has_column(data)
# value to use for rowname_col or groupname_col title
# Will use it for rowname_col only if groupname_col is undefined.
# Will use it only for the first groupname_col.
stub_label <- dt_stubhead_get(data)$label

if (is.null(stub_label)) {
# No label if stubhead label is undefined.
rowname_label <- ""
groupname_label <- ""
} else if (!is.null(column_groups)) {
rowname_label <- ""
groupname_label <- stub_label
} else {

locale_data <- locales[locales$locale == locale, ][1, ]

if (is.na(locale_data[["no_table_data_text"]])) {

lang_defs <- reactable::reactableLang()

} else {

lang_defs <-
reactable::reactableLang(
sortLabel = locale_data[["sort_label_text"]],
filterPlaceholder = "",
filterLabel = locale_data[["filter_label_text"]],
searchPlaceholder = locale_data[["search_placeholder_text"]],
searchLabel = locale_data[["search_placeholder_text"]],
noData = locale_data[["no_table_data_text"]],
pageNext = locale_data[["page_next_text"]],
pagePrevious = locale_data[["page_previous_text"]],
pageNumbers = locale_data[["page_numbers_text"]],
pageInfo = gsub("\\\\u2013", "\u2013", locale_data[["page_info_text"]]),
pageSizeOptions = locale_data[["page_size_options_text"]],
pageNextLabel = locale_data[["page_next_label_text"]],
pagePreviousLabel = locale_data[["page_previous_label_text"]],
pageNumberLabel = locale_data[["page_number_label_text"]],
pageJumpLabel = locale_data[["page_jump_label_text"]],
pageSizeOptionsLabel = locale_data[["page_size_options_label_text"]],
groupExpandLabel = "Toggle group",
detailsExpandLabel = "Toggle details",
selectAllRowsLabel = "Select all rows",
selectAllSubRowsLabel = "Select all rows in group",
selectRowLabel = "Select row"
)
}
rowname_label <- stub_label
groupname_label <- NULL
}


# Obtain the underlying data table (including group rows)
data_tbl0 <- dt_data_get(data = data)

#
# Only preserve columns that are not hidden (group cols will be added later)
#

data_tbl_vars <- dt_boxhead_get_vars_default(data = data)
data_tbl <- data_tbl0[, data_tbl_vars, drop = FALSE]

#nocov start

# Stop function if there are no visible columns
if (ncol(data_tbl) < 1) {

# Stop function if there are no visible columns
cli::cli_abort(c(
"When displaying an interactive gt table, there must be at least one visible column.",
"*" = "Check that the input data table has at least one column,",
"*" = "Failing that, look at whether all columns have been inadvertently hidden."
))
}

#nocov end

rownames_to_stub <- stub_rownames_has_column(data)
# use of special .rownames doesn't work.
# Workaround https://github.com/glin/reactable/issues/378
# rstudio/gt#1702
if (rownames_to_stub) {
rowname_col <- dt_boxhead_get_var_stub(data)
if (length(rowname_col) == 1) {
# avoid base R error when setting duplicate row names.
attr(data_tbl, "row.names") <- as.character(data$`_data`[[rowname_col]])
row_names <- as.character(data$`_data`[[rowname_col]])
# Convert to NA string to avoid wrong output.
# TODO figure out if there is a way to get the sub_missing value.
# With data$`_substitutions`
row_names <- dplyr::coalesce(row_names, "")
attr(data_tbl, "row.names") <- row_names
row_name_col_def <- list(reactable::colDef(
name = rowname_label
# TODO pass on other attributes of row names column if necessary.
))
# Create colDef row name with special ".rownames" from reactable.
names(row_name_col_def) <- ".rownames"

}
} else {
row_name_col_def <- NULL
}

# Obtain column label attributes
column_names <- dt_boxhead_get_vars_default(data = data)
column_labels <- dt_boxhead_get_vars_labels_default(data = data)
column_alignments <- dt_boxhead_get_vars_align_default(data = data)
column_groups <- dt_boxhead_get_vars_groups(data = data)
# if dt_boxhead_get_vars_groups is fixed, this will no longer be necessary.
if (identical(column_groups, NA_character_)) {
column_groups <- NULL
}

# Obtain widths for each visible column label
boxh <- dt_boxhead_get(data = data)
Expand Down Expand Up @@ -307,37 +293,47 @@ render_as_ihtml <- function(data, id) {
return cellInfo.value
}")
for (i in seq_along(column_groups)) {
group_col_defs[[i]] <- reactable::colDef(
name = "",
grouped = grp_fn,
# FIXME Should groups be sticky? (or provide a way to do this)
sticky = NULL
if (i == 1) {
# Use the stubhead label for the first group
group_label <- groupname_label
} else {
# by default, don't name groupname_col for consistency with non-interactive
group_label <- ""
}

group_col_defs[[i]] <-
reactable::colDef(
name = stub_label,
olivroy marked this conversation as resolved.
Show resolved Hide resolved
# The total number of rows is wrong in colGroup, possibly due to the JS fn
grouped = grp_fn,
# FIXME Should groups be sticky? (or provide a way to do this)
sticky = NULL
)

}
names(group_col_defs) <- column_groups
# Add group colDef to general col_def
col_defs <- c(col_defs, group_col_defs)

groupname_col <- column_groups
# for defaultExpanded = TRUE
expand_groupname_col <- TRUE

# modify data_tbl to include
data_tbl <- dplyr::bind_cols(
data_tbl,
data_tbl0[ , groupname_col, drop = FALSE]
)

# Set number of rows to FALSE, since it is inacurrate
# Otherwise, it just shows the number of groups
} else {
groupname_col <- NULL
group_col_defs <- NULL
expand_groupname_col <- FALSE
}
#
# Generate custom styles for `defaultColDef`
#

# Add group colDef and rowname colDef to general col_def
col_defs <- c(col_defs, group_col_defs, row_name_col_def)

styles_tbl <- dt_styles_get(data = data)
body_styles_tbl <- dplyr::filter(styles_tbl, locname %in% c("data", "stub"))
body_styles_tbl <- dplyr::arrange(body_styles_tbl, colnum, rownum)
Expand Down Expand Up @@ -378,6 +374,7 @@ render_as_ihtml <- function(data, id) {
collapse = ""
)

# TODO if `sub_missing()` is enabled gloablly, just use `na = ` here!
default_col_def <-
reactable::colDef(
style = reactable::JS(body_style_js_str),
Expand Down Expand Up @@ -808,3 +805,47 @@ create_footnotes_component_ihtml <- function(data) {
)
)
}

get_ihtml_translations <- function(locale) {
if (is.null(locale) || locale == "en") {

lang_defs <- reactable::reactableLang()

} else {

locale_data <- locales[locales$locale == locale, ][1, ]

if (is.na(locale_data[["no_table_data_text"]])) {

lang_defs <- reactable::reactableLang()

} else {

lang_defs <-
reactable::reactableLang(
sortLabel = locale_data[["sort_label_text"]],
filterPlaceholder = "",
filterLabel = locale_data[["filter_label_text"]],
searchPlaceholder = locale_data[["search_placeholder_text"]],
searchLabel = locale_data[["search_placeholder_text"]],
noData = locale_data[["no_table_data_text"]],
pageNext = locale_data[["page_next_text"]],
pagePrevious = locale_data[["page_previous_text"]],
pageNumbers = locale_data[["page_numbers_text"]],
pageInfo = gsub("\\\\u2013", "\u2013", locale_data[["page_info_text"]]),
pageSizeOptions = locale_data[["page_size_options_text"]],
pageNextLabel = locale_data[["page_next_label_text"]],
pagePreviousLabel = locale_data[["page_previous_label_text"]],
pageNumberLabel = locale_data[["page_number_label_text"]],
pageJumpLabel = locale_data[["page_jump_label_text"]],
pageSizeOptionsLabel = locale_data[["page_size_options_label_text"]],
groupExpandLabel = "Toggle group",
detailsExpandLabel = "Toggle details",
selectAllRowsLabel = "Select all rows",
selectAllSubRowsLabel = "Select all rows in group",
selectRowLabel = "Select row"
)
}
}
lang_defs
}
14 changes: 13 additions & 1 deletion tests/testthat/test-i_html.R
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,16 @@ test_that("Interactive tables won't fail when using different options", {
tbl_gt_i_25 <-
gt(mtcars_short, groupname_col = "vs") %>%
opt_interactive()

# #1758 tab_stubhead() respected (shown on top of group)
tbl_gt_i_26 <-
gt(mtcars_short, groupname_col = "vs", rownames_to_stub = T) %>%
tab_stubhead("stub label on top of vs") %>%
opt_interactive()
# #1758 NA rows show correctly + stubhead shows on top of row
tbl_gt_i_27 <-
gt(exibble, rowname_col = "char") %>%
tab_stubhead("stub label on top of rowname") %>%
opt_interactive()
capture_output(expect_no_error(tbl_gt_i_01))
capture_output(expect_no_error(tbl_gt_i_02))
capture_output(expect_no_error(tbl_gt_i_03))
Expand All @@ -106,4 +115,7 @@ test_that("Interactive tables won't fail when using different options", {
capture_output(expect_no_error(tbl_gt_i_23))
capture_output(expect_no_error(tbl_gt_i_24))
capture_output(expect_no_error(tbl_gt_i_25))
capture_output(expect_no_error(tbl_gt_i_26))
capture_output(expect_no_error(tbl_gt_i_27))

})
2 changes: 1 addition & 1 deletion tests/testthat/test-substitution.R
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ test_that("sub_missing() works correctly", {

# Expect an error when attempting to format a column
# that does not exist
expect_error(tab %>% sub_missing(columns = "num_3"))
expect_error(sub_missing(tab, columns = "num_3"))

expect_equal(
(tab %>%
Expand Down