Search code examples
rreactable

Reactable: Conditional styling in groups


I'm trying to combine the following two reactable features in R:

  • Conditional styling
  • Aggregating / grouping

My goal is to highlight the aggregated (sum) group headers with different shades of orange, depending on the group sum value. This way, it's easier to spot groups with high/low values.

I use only one color range c("#ffe4cc", "#ffb54d") for both cell values and group headers, e.g. 100 as cell value and 100 as group header result in the same color.


What I've tried so far (this is a simplified example, my real world problem needs highlighting over multiple columns/groups):

library(datasets)
library(reactable)

data('CO2')

get_orange <- function(x) rgb(colorRamp(c("#ffe4cc", "#ffb54d"))(x), maxColorValue = 255)

reactable(
  CO2,
  groupBy = c('Plant', 'Type', 'Treatment'),
  columns = list(
    conc = colDef(
      aggregate = 'mean'
    ),
    uptake = colDef(
      aggregate = 'sum',
      style = function(value) {
        normalized <- (value - min(CO2$uptake)) / (max(CO2$uptake) - min(CO2$uptake))
        color <- get_orange(normalized)
        list(background = color)
      }
    )
  )
)

Resulting in: Screenshot expected vs actual


> packageVersion('reactable')
[1] ‘0.2.3’

Solution

  • I found a workaround for the conditional styling in groups.

    By injecting a JS function in colDef(style=) of reactable, the color of each cell can be calculated.

    You can find the full example here.

    For each column you want with a conditional format, you have to bypass a JS() function, like so:

    for (column in dynamic_cols){
        script = paste("
          // source: https://glin.github.io/reactable/articles/examples.html#grouped-cell-rendering-1
          function(rowInfo) {
            // source: https://stackoverflow.com/a/44134328/4856719
            function hslToHex(h, s, l) {
              l /= 100;
              const a = s * Math.min(l, 1 - l) / 100;
              const f = n => {
            const k = (n + h / 30) % 12;
            const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
            return Math.round(255 * color).toString(16).padStart(2, '0');
              };
              return `#${f(0)}${f(8)}${f(4)}`;
            }
            var value = rowInfo.row['", column, "']
            var max = ", q, "
            var min = 0
            // pct_value = (value - min) * 100 / (max - min)
            pct_value = (Math.min(value, max) - min) * 100 / (max - min)
            // If value equals 0, set font color grey.
            if (value == 0) {
              var color = '#adafaa'
              var bg = '#fff'
            } else {
              var color = '#000000'
              var bg = hslToHex(40, pct_value, 95 - pct_value / 2)
            }
            return { color: color, backgroundColor: bg}
        }", sep="")
    
        dynamic_col_def[column] <- list(colDef(
          aggregate = 'sum', style = JS(script)
        ))
    }
    

    The injected JS function does consume rowInfo as input parameter and returns a named list. In this case, we return text color color and cell background color backgroundColor.

    With this workaround, you can either set a custom color range for each column in the table or combine multiple columns to one color range.

    The thread on github/glin/reactable can be found here.