glin / reactable

Interactive data tables for R
https://glin.github.io/reactable
Other
627 stars 80 forks source link

Conditional styling in groups #200

Open fbardos opened 3 years ago

fbardos commented 3 years ago

Question originally posted on Stack Overflow.


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

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. Question: Is this possible with today's features of reactable?

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’
glin commented 3 years ago

It's possible to conditionally style aggregated cells, but you have to use a JavaScript style function. The grouping is all done by JavaScript in the browser, so there's unfortunately no way to set aggregated cell styles from R.

I just realized I forgot to add an example of styling aggregated cells in the docs, so here's a quick example until I can fix the docs:

reactable(
  sleep,
  groupBy = "group",
  columns = list(
    extra = colDef(
      aggregate = "mean",
      # JS style function that gets applied to both regular and aggregated cells.
      # rowInfo.row['extra'] contains the value of the cell in the "extra" column.
      style = JS("function(rowInfo) {
        var value = rowInfo.row['extra']
        if (value > 0) {
          var color = '#008000'
        } else if (value < 0) {
          var color = '#e00000'
        } else {
          var color = '#000000'
        }
        return { color: color, fontFamily: 'monospace' }
      }")
    )
  )
)

reactable output

There's also the reference on JavaScript style functions for more information, like how to tell if a cell is aggregated or not, or how to find its groupBy nesting level.

As for color scale highlighting, I think that'll be the hard part. JavaScript doesn't have any built-in color manipulation utilities as far as I know, so you'll have to write your own thing or use an external library to do color scales. I haven't had to do this yet, so I don't have any examples on hand. From a quick search, you might be able to do color scales with a library like chroma.js.

fbardos commented 3 years ago

Thank you @glin for your help. I've manged to create a working example with a (limited) color scale highlighting, using HSL color values:

image

If you like, I could create a PR for either

  1. modification to the example section in vignettes/examples.Rmd, or
  2. a new demo with a basic Shiny Dashboard.
glin commented 2 years ago

@fbardos nice! Is that using an external JS library, or is it creating a color scale manually? Can you post your example code? I'm not sure where an example like this would fit best in the docs. If the code is fairly simple, the examples doc would work. If it's more complicated, then it may fit better in either the cookbook or in its own separate doc.

fbardos commented 2 years ago

You can check my example here.

Features:

I'm not experienced in JavaScript. Maybe it is not performant to create the function hslToHex() for every column in an iteration (see Line 87).

Is that using an external JS library, or is it creating a color scale manually?

It is creating the color scale manually according to this formula:

HSL(40, pct_value, 95 - pct_value / 2), whereas

pct_value: Is the current value expressed as percent (lowest value = 0, highest value = 90th percentile)

I'm not sure where an example like this would fit best in the docs.

You can decide. I could make an example with less code if I do not use Shiny. I would then still use dynamic column definitions, but within a fixed date range.