Skip to content

Commit 13cdba8

Browse files
authored
Merge pull request #246 from microsoft/copilot/fix-245
Add show_rows parameter to create_dt() function for customizable default row display
2 parents 3e228ce + e74069e commit 13cdba8

File tree

3 files changed

+274
-14
lines changed

3 files changed

+274
-14
lines changed

R/create_dt.R

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,80 @@
1111
#' for more.
1212
#'
1313
#' @param x Data frame to be passed through.
14-
#' @param rounding Numeric vector to specify the number of decimal points to display
14+
#' @param rounding Numeric vector to specify the number of decimal points to display.
15+
#' Can also be a named list to specify different rounding for specific columns,
16+
#' e.g., `list("Sepal.Width" = 1, "Sepal.Length" = 2)`. When a list is provided,
17+
#' columns not specified in the list will use the default of 1 decimal place.
1518
#' @param freeze Number of columns from the left to 'freeze'. Defaults to 2,
1619
#' which includes the row number column.
1720
#' @param percent Logical value specifying whether to display numeric columns
1821
#' as percentages.
22+
#' @param show_rows Numeric value or "All" to specify the default number of rows
23+
#' to display. Defaults to 10. When set to a specific number, that number will be
24+
#' the first option in the length menu. When set to "All", all rows will be shown
25+
#' by default.
1926
#'
2027
#' @family Import and Export
2128
#'
2229
#' @examples
2330
#' out_tb <- hrvar_count(sq_data, hrvar = "Organization", return = "table")
31+
#' out_tb$prop <- out_tb$n / sum(out_tb$n)
2432
#' create_dt(out_tb)
2533
#'
34+
#' # Show 25 rows by default
35+
#' create_dt(out_tb, show_rows = 25)
36+
#'
37+
#' # Show all rows by default
38+
#' create_dt(out_tb, show_rows = "All")
39+
#'
40+
#' # Apply different rounding to specific columns
41+
#' create_dt(out_tb, rounding = list("n" = 0, "prop" = 3))
42+
#'
43+
#' # Mix of list and default rounding
44+
#' create_dt(out_tb, rounding = list("prop" = 3)) # Other numeric columns get 1 dp
45+
#'
2646
#' @return
2747
#' Returns an HTML widget displaying rectangular data.
2848
#'
2949
#' @export
30-
create_dt <- function(x, rounding = 1, freeze = 2, percent = FALSE){
50+
create_dt <- function(x, rounding = 1, freeze = 2, percent = FALSE, show_rows = 10){
51+
52+
# Construct length menu based on show_rows parameter
53+
if(show_rows == "All" || show_rows == -1) {
54+
length_menu_values <- c(-1, 25, 10, 50)
55+
length_menu_labels <- c("All", 25, 10, 50)
56+
} else {
57+
# Ensure show_rows is numeric
58+
show_rows <- as.numeric(show_rows)
59+
# Create menu with show_rows first, then other common options
60+
other_options <- c(10, 25, 50, -1)
61+
other_options <- other_options[other_options != show_rows]
62+
length_menu_values <- c(show_rows, other_options)
63+
64+
other_labels <- c(10, 25, 50, "All")
65+
other_labels <- other_labels[other_labels != show_rows]
66+
length_menu_labels <- c(show_rows, other_labels)
67+
}
3168

3269
# Round all numeric to "rounding" number of dp
3370
num_cols <- dplyr::select(x, where(is.numeric)) %>% names()
3471

72+
# Handle rounding parameter - can be numeric or named list
73+
if(is.list(rounding)) {
74+
# When rounding is a list, extract specified columns and their rounding values
75+
specified_cols <- intersect(names(rounding), num_cols)
76+
rounding_list <- rounding[specified_cols]
77+
# Default rounding for unspecified numeric columns
78+
default_rounding <- 1
79+
unspecified_cols <- setdiff(num_cols, specified_cols)
80+
} else {
81+
# When rounding is numeric (backward compatibility)
82+
rounding_list <- NULL
83+
default_rounding <- rounding
84+
unspecified_cols <- num_cols
85+
specified_cols <- character(0)
86+
}
87+
3588
if(length(num_cols) == 0){ # No numeric columns
3689

3790
out <-
@@ -44,8 +97,8 @@ create_dt <- function(x, rounding = 1, freeze = 2, percent = FALSE){
4497
fixedColumns = list(leftColumns = freeze),
4598
scrollX = TRUE,
4699
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
47-
lengthMenu = list(c(10, 25, 50, -1),
48-
c(10, 25, 50, "All"))
100+
lengthMenu = list(length_menu_values,
101+
length_menu_labels)
49102
)
50103
)
51104

@@ -61,18 +114,39 @@ create_dt <- function(x, rounding = 1, freeze = 2, percent = FALSE){
61114
fixedColumns = list(leftColumns = freeze),
62115
scrollX = TRUE,
63116
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
64-
lengthMenu = list(c(10, 25, 50, -1),
65-
c(10, 25, 50, "All"))
117+
lengthMenu = list(length_menu_values,
118+
length_menu_labels)
66119
)
67-
) %>%
68-
DT::formatRound(columns = num_cols, rounding)
120+
)
69121

70-
if(percent == TRUE){
122+
# Apply rounding - handle both single value and list cases
123+
if(length(unspecified_cols) > 0) {
124+
# Apply default rounding to unspecified columns
125+
out <- out %>% DT::formatRound(columns = unspecified_cols, default_rounding)
126+
}
71127

72-
out <-
73-
out %>%
74-
DT::formatPercentage(columns = num_cols, rounding)
128+
if(length(specified_cols) > 0) {
129+
# Apply specific rounding to specified columns
130+
for(col in specified_cols) {
131+
out <- out %>% DT::formatRound(columns = col, rounding_list[[col]])
132+
}
133+
}
75134

135+
if(percent == TRUE){
136+
# Apply percentage formatting with appropriate rounding
137+
if(is.list(rounding)) {
138+
# Apply specific rounding to specified columns
139+
for(col in specified_cols) {
140+
out <- out %>% DT::formatPercentage(columns = col, rounding_list[[col]])
141+
}
142+
# Apply default rounding to unspecified columns
143+
if(length(unspecified_cols) > 0) {
144+
out <- out %>% DT::formatPercentage(columns = unspecified_cols, default_rounding)
145+
}
146+
} else {
147+
# Apply same rounding to all numeric columns (backward compatibility)
148+
out <- out %>% DT::formatPercentage(columns = num_cols, rounding)
149+
}
76150
}
77151
}
78152

man/create_dt.Rd

Lines changed: 23 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-create_dt.R

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See LICENSE.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
test_that("create_dt show_rows parameter works correctly", {
7+
# Create a simple test dataframe
8+
test_data <- data.frame(
9+
PersonId = 1:100,
10+
Organization = paste0("Org", 1:100),
11+
Value = rnorm(100),
12+
stringsAsFactors = FALSE
13+
)
14+
15+
# Test default behavior (show_rows = 10)
16+
dt_default <- create_dt(test_data)
17+
expect_s3_class(dt_default, "datatables")
18+
19+
# Test show_rows = 25
20+
dt_25 <- create_dt(test_data, show_rows = 25)
21+
expect_s3_class(dt_25, "datatables")
22+
23+
# Test show_rows = "All"
24+
dt_all <- create_dt(test_data, show_rows = "All")
25+
expect_s3_class(dt_all, "datatables")
26+
27+
# Test show_rows = -1 (equivalent to "All")
28+
dt_all_numeric <- create_dt(test_data, show_rows = -1)
29+
expect_s3_class(dt_all_numeric, "datatables")
30+
})
31+
32+
test_that("create_dt show_rows parameter constructs correct lengthMenu", {
33+
# Create a simple test dataframe
34+
test_data <- data.frame(
35+
A = 1:5,
36+
B = letters[1:5],
37+
stringsAsFactors = FALSE
38+
)
39+
40+
# Test that function runs without error for different show_rows values
41+
expect_no_error(create_dt(test_data, show_rows = 10))
42+
expect_no_error(create_dt(test_data, show_rows = 25))
43+
expect_no_error(create_dt(test_data, show_rows = 50))
44+
expect_no_error(create_dt(test_data, show_rows = "All"))
45+
expect_no_error(create_dt(test_data, show_rows = -1))
46+
})
47+
48+
test_that("create_dt rounding parameter works with numeric values (backward compatibility)", {
49+
# Create test data with numeric columns
50+
test_data <- data.frame(
51+
Name = c("A", "B", "C"),
52+
Value1 = c(1.23456, 2.78901, 3.45678),
53+
Value2 = c(0.1234, 0.5678, 0.9012),
54+
Count = c(10L, 20L, 30L),
55+
stringsAsFactors = FALSE
56+
)
57+
58+
# Test with numeric rounding values
59+
expect_no_error(create_dt(test_data, rounding = 1))
60+
expect_no_error(create_dt(test_data, rounding = 2))
61+
expect_no_error(create_dt(test_data, rounding = 0))
62+
63+
# Should return datatables object
64+
result <- create_dt(test_data, rounding = 2)
65+
expect_s3_class(result, "datatables")
66+
})
67+
68+
test_that("create_dt rounding parameter works with named lists", {
69+
# Create test data with numeric columns
70+
test_data <- data.frame(
71+
Name = c("A", "B", "C"),
72+
Value1 = c(1.23456, 2.78901, 3.45678),
73+
Value2 = c(0.1234, 0.5678, 0.9012),
74+
Count = c(10L, 20L, 30L),
75+
stringsAsFactors = FALSE
76+
)
77+
78+
# Test with list rounding
79+
expect_no_error(create_dt(test_data, rounding = list("Value1" = 1, "Value2" = 3)))
80+
expect_no_error(create_dt(test_data, rounding = list("Count" = 0)))
81+
expect_no_error(create_dt(test_data, rounding = list("Value1" = 2, "Value2" = 1, "Count" = 0)))
82+
83+
# Should return datatables object
84+
result <- create_dt(test_data, rounding = list("Value1" = 1, "Value2" = 3))
85+
expect_s3_class(result, "datatables")
86+
})
87+
88+
test_that("create_dt handles edge cases for rounding parameter", {
89+
# Test data with only non-numeric columns
90+
non_numeric_data <- data.frame(
91+
Name = c("A", "B", "C"),
92+
Category = c("X", "Y", "Z"),
93+
stringsAsFactors = FALSE
94+
)
95+
96+
# Should work with non-numeric data regardless of rounding parameter
97+
expect_no_error(create_dt(non_numeric_data, rounding = 2))
98+
expect_no_error(create_dt(non_numeric_data, rounding = list("Name" = 1)))
99+
100+
# Test data with numeric columns
101+
numeric_data <- data.frame(
102+
Value1 = c(1.23, 2.34),
103+
Value2 = c(3.45, 4.56)
104+
)
105+
106+
# Test with empty list
107+
expect_no_error(create_dt(numeric_data, rounding = list()))
108+
109+
# Test with list containing non-existent column names
110+
expect_no_error(create_dt(numeric_data, rounding = list("NonExistent" = 2, "Value1" = 1)))
111+
112+
# Should still return datatables object
113+
result <- create_dt(numeric_data, rounding = list("NonExistent" = 2, "Value1" = 1))
114+
expect_s3_class(result, "datatables")
115+
})
116+
117+
test_that("create_dt rounding works with percentage formatting", {
118+
# Create test data
119+
test_data <- data.frame(
120+
Category = c("A", "B"),
121+
Rate1 = c(0.234, 0.567),
122+
Rate2 = c(0.123, 0.789),
123+
stringsAsFactors = FALSE
124+
)
125+
126+
# Test numeric rounding with percentages
127+
expect_no_error(create_dt(test_data, rounding = 2, percent = TRUE))
128+
129+
# Test list rounding with percentages
130+
expect_no_error(create_dt(test_data, rounding = list("Rate1" = 1, "Rate2" = 3), percent = TRUE))
131+
132+
# Should return datatables object
133+
result <- create_dt(test_data, rounding = list("Rate1" = 1, "Rate2" = 3), percent = TRUE)
134+
expect_s3_class(result, "datatables")
135+
})
136+
137+
test_that("create_dt preserves all other functionality with new rounding feature", {
138+
# Create test data
139+
test_data <- data.frame(
140+
Name = paste0("Item", 1:20),
141+
Value1 = runif(20, 1, 10),
142+
Value2 = runif(20, 0, 1),
143+
stringsAsFactors = FALSE
144+
)
145+
146+
# Test that all combinations work
147+
expect_no_error(create_dt(test_data,
148+
rounding = list("Value1" = 2, "Value2" = 3),
149+
freeze = 1,
150+
percent = FALSE,
151+
show_rows = 15))
152+
153+
expect_no_error(create_dt(test_data,
154+
rounding = list("Value2" = 4),
155+
freeze = 2,
156+
percent = TRUE,
157+
show_rows = "All"))
158+
159+
# All should return datatables objects
160+
result1 <- create_dt(test_data, rounding = list("Value1" = 2), freeze = 1)
161+
result2 <- create_dt(test_data, rounding = 2, freeze = 1) # backward compatibility
162+
163+
expect_s3_class(result1, "datatables")
164+
expect_s3_class(result2, "datatables")
165+
})

0 commit comments

Comments
 (0)