reactable icon indicating copy to clipboard operation
reactable copied to clipboard

Change cell styling based on multiple columns

Open jthomasmock opened this issue 5 years ago • 3 comments

Issue:

Using multiple columns as arguments to style a single column's cells.

Version: reactable_0.2.0

For this reprex I'm trying to use one column to create a bar length, and then color that bar according to a second column. Alternatively, this could be assigning say, bold face to a cell and then coloring it according to a secondary column, or some other combination of inputs to alter a single cell.

I was able to successfully use colDef to accomplish this, but had to rely on absolute position of the row within the dataset with the index argument.

Is there a better way to use multiple columns as arguments within a custom function? I tried to do this with JS via cellInfo.row['col_name'] but couldn't get that workflow to succeed - likely due to errors on my part.

library(dplyr)
library(reactable)
library(htmltools)

# Verbatim borrowed from your docs
bar_chart <- function(label, width = "100%", height = "14px", fill = "#00bfc4", background = NULL) {
  bar <- div(style = list(background = fill, width = width, height = height))
  chart <- div(style = list(flexGrow = 1, marginLeft = "6px", background = background), bar)
  div(style = list(display = "flex", alignItems = "center"), label, chart)
}

mtcars %>% 
  select(cyl, mpg) %>% 
  arrange(cyl) %>% 
  # Just using this to find the specific placement of which rows to "cut" at
  mutate(row_n = row_number()) %>% 
  reactable(
    defaultPageSize = 20,
    columns = list(
      mpg = colDef(
        name = "mpg",
        defaultSortOrder = "desc",
        cell = function(value, index) {
          width <- paste0(value * 100 / max(mtcars$mpg), "%")
          value <- format(value, big.mark = ",")
          value <- format(value, width = 9, justify = "right")
         # Relying on absolute position rather than matching logic
         # I don't know if it's possible to reference multiple columns here
         # my understanding is that the mpg col is passed through, but nothing else
          color_fill <- if(index < 12) {
            "blue"
          } else if (index >= 12 & index <=18) {
            "yellow"
          } else {
            "red"
          }
          bar_chart(value, width = width, fill = color_fill)
        },
        align = "left",
        style = list(fontFamily = "monospace", whiteSpace = "pre")
      )
    )
  )

Created on 2020-07-03 by the reprex package (v0.3.0)

jthomasmock avatar Jul 03 '20 16:07 jthomasmock

In an R render function or style function, you can use the row index argument to get the other values in the row. For a really simplified example:

library(reactable)

data <- mtcars[, c("cyl", "mpg")]

reactable(
  data,
  columns = list(
    mpg = colDef(
      cell = function(value, index) {
        # Get the cyl value from the same row
        cyl <- data$cyl[index]
        sprintf("%s (cyl=%s)", value, cyl)
      },
      style = function(value, index) {
        cyl <- data$cyl[index]
        if (cyl == 6) {
          list(fontWeight = "bold")
        }
      }
    )
  )
)

screenshot of result

In a JavaScript render or style function, the equivalent would be using cellInfo.row to get other values in the row:

library(reactable)

data <- mtcars[, c("cyl", "mpg")]

reactable(
  data,
  columns = list(
    mpg = colDef(
      cell = JS("function(cellInfo) {
        // Get the cyl value from the same row
        const cyl = cellInfo.row.cyl
        return cellInfo.value + ' (cyl=' + cyl + ')'
      }"),
      style = JS("function(cellInfo) {
        if (cellInfo.row.cyl === 6) {
          return { fontWeight: 'bold' }
        }
      }")
    )
  )
)

I think your example is really close - you just have to assign the data to a variable so you can access it. Here's my slightly modified example:

library(dplyr)
library(reactable)
library(htmltools)

bar_chart <- function(label, width = "100%", height = "14px", fill = "#00bfc4", background = NULL) {
  bar <- div(style = list(background = fill, width = width, height = height))
  chart <- div(style = list(flexGrow = 1, marginLeft = "6px", background = background), bar)
  div(style = list(display = "flex", alignItems = "center"), label, chart)
}

data <- mtcars %>% 
  select(cyl, mpg) %>% 
  arrange(cyl)

reactable(
  data,
  defaultPageSize = 20,
  columns = list(
    mpg = colDef(
      name = "mpg",
      defaultSortOrder = "desc",
      cell = function(value, index) {
        width <- paste0(value * 100 / max(mtcars$mpg), "%")
        value <- format(value, big.mark = ",")
        value <- format(value, width = 9, justify = "right")
        # Color based on the row's cyl value
        cyl <- data$cyl[index]
        color_fill <- if (cyl == 4) {
          "blue"
        } else if (cyl == 6) {
          "yellow"
        } else {
          "red"
        }
        bar_chart(value, width = width, fill = color_fill)
      },
      align = "left",
      style = list(fontFamily = "monospace", whiteSpace = "pre")
    )
  )
)

Since this is pretty common to do when making tables, I've added an example of accessing data from other columns using both R and JS: https://glin.github.io/reactable/articles/cookbook/cookbook.html#show-data-from-other-columns

glin avatar Jul 06 '20 04:07 glin

In an R render function or style function, you can use the row index argument to get the other values in the row.

@glin Is there a way to use the column index as well?

Johan-rosa avatar Dec 07 '22 21:12 Johan-rosa

@Johan-rosa Not exactly, but there is a column name/ID available as an optional 3rd argument that you could use directly, or use to get the column index. Check out the Cells - R render function docs.

glin avatar Dec 10 '22 22:12 glin