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

Column formatting within colGroup() #352

Open
JoeMarangos opened this issue Dec 20, 2023 · 2 comments
Open

Column formatting within colGroup() #352

JoeMarangos opened this issue Dec 20, 2023 · 2 comments

Comments

@JoeMarangos
Copy link

JoeMarangos commented Dec 20, 2023

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

@ArthurAndrews
Copy link

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))

@glin
Copy link
Owner

glin commented Jan 29, 2024

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)
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants