4

I have a dataframe column that I'm currently formatting using the formattable::color_tile function (below):

color_tile( "red", "springgreen" )

My issue with this is that the values near the middle are an ugly brown color, and I'd ideally like it to be a red-amber-green gradient, but color_tile seems to only be able to take min.color and max.color parameters - is it possible to use a 3rd color with either this or similar formatting functions in R?

henrygd
  • 41
  • 1
  • 4

5 Answers5

8

It doesn't look like the function is designed to handle more than two colors, but you can make your own building on that template.

color_tile2 <- function (...) {
  formatter("span", style = function(x) {
    style(display = "block",
          padding = "0 4px", 
          `border-radius` = "4px", 
          `background-color` = csscolor(matrix(as.integer(colorRamp(...)(normalize(as.numeric(x)))), 
                                               byrow=TRUE, dimnames=list(c("red","green","blue"), NULL), nrow=3)))
  })}

which can be used like

formattable(mtcars, list(mpg = color_tile2(c("white", "pink"))))
formattable(mtcars, list(mpg = color_tile2(c("blue", "green", "pink"))))
Community
  • 1
  • 1
MrFlick
  • 195,160
  • 17
  • 277
  • 295
  • This is giving me the error message `Error in .approxfun: (list) object cannot be coerced to type 'double'`. Here's my UI code `ft <- formattable( individuals, list( Quotes = color_tile( "white", "orange" ), Mentions = color_tile2(c( "red", "blue", "green" )), Retweets = color_tile( "white", "orange" ), Replies To = color_tile( "white", "orange" ) ) )` – henrygd Apr 18 '18 at 15:16
  • 1
    I would need some kind of reproducible example. This works for me: `formattable(mtcars, list(mpg = color_tile2(c("blue", "green", "pink")), drat= color_tile("white","orange")))` – MrFlick Apr 18 '18 at 16:30
3

Determine which row numbers you want between color 1 and 2 and which row numbers for between color 2 and 3. Then call color_tile twice. For example

formattable(x, 
        list(
          area(col = 2, row = c(1,3,5,7,8,9,10,13,14,15)) ~ color_tile("red", "white"),
          area(col = 2, row = c(2,4,6,11,12,16)) ~ color_tile("white","green")
        ))

Won't fix it perfectly, since it won't keep the relative intensity of the colors on either side

Rui Barradas
  • 70,273
  • 8
  • 34
  • 66
2

In an issue' entry of the github' formattable site, I found this which seems useful and solved my problem to have a column color coded on continuous scale from negative to positive as red to green, without having the "brown" in the middle (this will deliver "transparent"):

library(dplyr)
library(kableExtra)
library(formattable)

x = currency(c(1000000,
                 -3000,
                400000,
                800000,
                 -1700,
                     0,
                 50000))

x = ifelse(
  x <= 0.0, 
  color_tile("red", "transparent")(x*c(x<=0)),
  color_tile("transparent", "green")(x*c(x>=0)))

x %>% 
  kable(escape = F) %>% 
  kable_styling(bootstrap_options = c("striped", "hover"), 
                full_width = F)

This is the relevant link: https://github.com/renkun-ken/formattable/issues/102#issuecomment-408649019

Matteo Castagna
  • 472
  • 3
  • 13
1

Based on @cmilando, I rewrite the function so that the colors reflect negative and positive numbers better, a bit manually though :D

library(tidyverse)
library(RColorBrewer)
library(formattable)
library(kableExtra)
library(purrr)

# --------------------
# brewer.pal(10,"RdYlGn")

my_color_tile <- function() {
  
  return_col <- function(y) 
    map_chr(y,function(x) case_when(x > 80  ~ "#006837",
              x > 60  ~ "#1A9850",
              x > 40  ~ "#66BD63",
              x > 20  ~ "#A6D96A",
              x >= 0  ~ "#D9EF8B",
              x >= -20  ~ "#FEE08B",
              x >= -40  ~ "#FDAE61",
              x >= -60  ~ "#F46D43",
              x >= -80  ~ "#D73027",
              x >= -100  ~ "#A50026"
              ))
  
  formatter("span", 
            style = function(y) style(
              display = "block",
              padding = "0 4px",
              "border-radius" = "4px",
              "color" = ifelse( return_col(y) %in% c("#A50026","#D73027","#F46D43","#006837","#1A9850","#66BD63"),
                                csscolor("white"), csscolor("black")),
              "background-color" = return_col(y)
            )
  )
}

# --------------------
data.frame(value = c(seq(-100,100,10))) %>% 
  arrange(desc(value)) %>%  
  formattable(., list(
    area(col = 1) ~ my_color_tile()))

0

Something like this might work, leveraging RColorBrewer

color_tile3 <- function(fun = "comma", digits = 0, palette = 'RdBu', n = 9) {
  fun <- match.fun(fun)

  stopifnot(n >= 5)
  
  return_cut <- function(y) 
    cut(y, breaks = quantile(y, probs = 0:n/n, na.rm = T),
        labels = 1:n, ordered_result = T, include.lowest = T)
  
  return_col <- function(y) 
      RColorBrewer::brewer.pal(n, palette)[as.integer(return_cut(y))]
  
  formatter("span", x ~ fun(x, digits = digits),
            style = function(y) style(
              display = "block",
              padding = "0 4px",
              "border-radius" = "4px",
              "color" = ifelse( return_cut(y) %in% c(1, 2, n-1, n),
                                csscolor("white"), csscolor("black")),
              "background-color" = return_col(y)
            )
  )
}

Use case:

library(tidyverse)
library(RColorBrewer)

mtcars[, 1:5] %>%
  corrr::correlate() %>%
  formattable(., list(
    `rowname` = formatter("span", style = ~ style(color = "grey", 
                                                  font.weight = "bold")), 
    area(col = 2:6) ~ color_tile3(digits = 2)))

Output:

Here's a link to the output mtcars_color3 which looks like this:

mtcars_color3_embedded

micstr
  • 5,080
  • 8
  • 48
  • 76
cmilando
  • 46
  • 4