Column formatting within colGroup()
JoeMarangos opened this issue · 2 comments
Hi everyone,
I've only just switched over from DT so I may be missing something obvious here.
I am having an issue with using format = colFormat within colGroup and have noticed that it can only be used within colDef.
I believe this to be a bit of an issue when you are dealing with Shiny applications as sometimes with dynamic filters or databases you may have changing columns.
As an example, lets say I have a data frame that is generated within the Shiny app. The first 5 columns are character, but the last 10 columns are percentages. Lets say the next time I generate the table the last 6 columns are percentages.
Using the colDef is impractical in this scenario as I would need to type out:
columns = list(Percentage1 = colDef(format = colFormat(perecent = T)),
Percntage2 = colDef(format = colFormat(perecent = T))
)
And so on... This also doesn't work if I have a dynamic database that is constantly changing its columns as otherwise I would have to have a renderReactable object for every permutation.
When I'm determining "sticky" columns the colGroup works really well. See the example code bellow for how I can use this on a dynamic database:
column_maker <- grep("Percentage",colnames(df),values=T)
columnGroups = list(
colGroup(name = "LOCKED",columns = c(column_marker),sticky = "left")
)
In this scenario I can use columnGroups and a grep function to essentially ensure that the reactable dynamically updates the sticky columns within a Shiny observeEvent. It would be great if format could be applied in this way so that:
columnGroups = list(
colGroup(name = "LOCKED",columns = c(column_marker),sticky = "left",format = colFormat(percent = T))
)
Am I missing something painfully stupid here or is this a current limitation of reactable? Would love for there to be a workaround so I can create a more dynamic Shiny environment.
Edit: Sorry, the same would also apply to things such as "footer = " ect.
Thanks,
Joe
The columns
argument is a list so you can write code to generate it based on the data frame column names, types, and values. Seems fully flexible to me. For example, if you wanted to format numeric values one way and character another:
library(tidyverse)
library(reactable)
columns_input_fn <- function(x) {
if (is.numeric(x)) {
colDef(format = colFormat(digits = 4))
} else {
colDef()
}
}
df <- iris |> head(5)
reactable(df, columns = df |> map(columns_input_fn))
If I'm understanding correctly, I think @ArthurAndrews's suggestion of programmatically generating columns
would work for these dynamic columns cases.
To add onto that, I have an unpublished doc with several examples of generating columns. Here are a few more examples that might help.
Reusing column definitions
library(reactable)
df <- data.frame(
A = runif(100),
B = rnorm(100),
C = rnorm(n = 100, mean = 10, sd = 2),
D = rnorm(n = 100, mean = 10, sd = 2)
)
reactable(df, columns = list(
A = colDef(format = colFormat(percent = TRUE, digits = 2)),
B = colDef(format = colFormat(percent = TRUE, digits = 2)),
C = colDef(format = colFormat(percent = FALSE, digits = 2)),
D = colDef(format = colFormat(percent = FALSE, digits = 2))
))
Use a default column definition:
reactable(
df,
defaultColDef = colDef(format = colFormat(percent = TRUE, digits = 2)),
columns = list(
C = colDef(format = colFormat(percent = FALSE, digits = 2)),
D = colDef(format = colFormat(percent = FALSE, digits = 2))
)
)
Create and reuse column formatters:
pct_format <- colFormat(percent = TRUE, digits = 2)
num_format <- colFormat(digits = 2)
reactable(
df,
columns = list(
A = colDef(format = pct_format),
B = colDef(format = pct_format),
C = colDef(format = num_format),
D = colDef(format = num_format)
)
)
Use a function to generate column formatters:
num_format <- function(percent = FALSE) {
colFormat(percent = percent, digits = 2)
}
reactable(
df,
columns = list(
A = colDef(format = num_format(percent = TRUE)),
B = colDef(format = num_format(percent = TRUE)),
C = colDef(format = num_format()),
D = colDef(format = num_format())
)
)
Use a function to generate column definitions:
num_column <- function(percent = FALSE) {
colDef(format = colFormat(percent = percent, digits = 2))
}
reactable(
df,
columns = list(
A = num_column(percent = TRUE),
B = num_column(percent = TRUE),
C = num_column(),
D = num_column()
)
)
Use a custom render function that formats columns according to column name:
reactable(
df,
defaultColDef = colDef(
cell = function(value, index, name) {
suffix <- ""
# Format percent columns
if (name %in% c("A", "B")) {
value <- value * 100
suffix <- "%"
}
value <- formatC(value, digits = 2, format = "f")
paste0(value, suffix)
}
)
)
Use custom format/render functions in the default column definition:
# Generic formatting functions for percent or numeric values
fmt_pct <- function(value) paste0(formatC(value * 100, digits = 2, format = "f"), "%")
fmt_num <- function(value) formatC(value, digits = 2, format = "f")
reactable(
df,
defaultColDef = colDef(
cell = function(value, index, name) {
if (name %in% c("A", "B")) {
fmt_pct(value)
} else {
fmt_num(value)
}
}
)
)
Dynamic column definitions
Assigning to a list:
columns <- list()
columns[["cyl"]] <- colDef(name = "Cylinders")
name <- "disp"
columns[[name]] <- colDef(name = "Displacement")
reactable(mtcars, columns = columns)
Using setNames()
:
names <- c("cyl", "disp")
columns <- setNames(
list(
colDef(name = "Cylinders"),
colDef(name = "Displacement")
),
names
)
reactable(mtcars, columns = columns)
reactable(
mtcars,
columns = setNames(
list(
colDef(name = "Cylinders"),
colDef(name = "Displacement")
),
names
)
)
Adding columns
Use c()
to combine lists:
# Separate named list of colDefs to add. Could be an empty list as well.
extra_columns <- list(
cyl = colDef(name = "Cylinders"),
disp = colDef(name = "Displacement")
)
reactable(
mtcars,
columns = c(
list(
mpg = colDef(name = "Miles per gallon"),
hp = colDef(name = "Horsepower")
),
extra_columns
)
)
Column generating function
library(reactable)
data <- data.frame(
Category = c("A", "B", "C"),
January = c(1000, 4999, 42345),
February = c(2000, 6342, 56734),
March = c(3000, 6734, 75342)
)
column_defs <- function(data) {
lapply(data, function(x) {
if (is.numeric(x)) {
# Numeric columns
colDef(format = colFormat(separators = TRUE))
} else {
# Default columns
colDef()
}
})
}
reactable(
data,
columns = column_defs(data)
)