-->

custom fill color in ggvis (and other options)

2019-01-23 18:40发布

问题:

I'm trying to use ggvis to create a NFL strength-of-schedule chart for the 2014 season. The data comes from FootballOutsiders.com, and later I'll make a Shiny app that automatically scrapes data from the website as it's updated during the season. The example below is pretty close, but I'd like to make a few modifications. I want to...

  1. Include the numeric value of "defense" in each cell of the chart, so the visualization resembles the original "df" data frame.

  2. Customize the color scale so positive values are increasingly orange and negative values are increasingly blue (i.e. more negative = more blue).

  3. An alternative to #2 could be to make a gradient from orange to blue, and make the opacity decrease to 0.5 as the value of "defense" approaches zero.

  4. Be able to choose the color of NA, because it currently shows up as black in the chart.

I've been tinkering around with add_scale() and props(), but nothing has been working so far.

Here's the chart:

Here's the data:

df <- structure(list(team = c("ARI", "ATL", "BAL", "BUF", "CAR", "CHI", 
"CIN", "CLE", "DAL", "DEN", "DET", "GB", "HOU", "IND", "JAX", 
"KC", "MIA", "MIN", "NE", "NO", "NYG", "NYJ", "OAK", "PHI", "PIT", 
"SD", "SEA", "SF", "STL", "TB", "TEN", "WAS"), w1 = c(17.5, -5.8, 
-12.6, 8.7, -6.8, -13.8, -8.7, 4, -4.6, 0.9, -11.4, -25.9, 4.2, 
-0.2, 4.9, 4.2, 4.2, -5.7, 2.4, 13.5, -0.8, 10.3, -5.6, 10.9, 
8.2, -16.4, 14.4, 13.8, 10.5, -15.7, -6.7, 2.5), w2 = c(-11.4, 
-12.6, 4, 2.4, -0.8, -4.6, 13.5, -5.8, 4.2, -6.7, -15.7, -5.6, 
10.3, 4.9, 4.2, -0.2, -13.8, 4.2, 10.5, 8.2, -16.4, 14.4, 2.5, 
0.9, -8.7, -25.9, 17.5, 8.7, -6.8, -5.7, 13.8, 10.9), w3 = c(-4.6, 
-6.8, 8.2, 17.5, 4, -5.6, 4.2, -8.7, -5.7, -25.9, 14.4, -0.8, 
-11.4, 10.9, 0.9, 2.4, -6.7, -5.8, 10.3, 10.5, 2.5, 8.7, 4.2, 
4.2, -15.7, -13.8, -0.2, -16.4, 13.8, 13.5, -12.6, 4.9), w4 = c(NA, 
10.5, -15.7, 2.5, -8.7, 14.4, NA, NA, -5.8, NA, -5.6, 8.7, -13.8, 
4.2, 17.5, 4.2, 10.3, 13.5, -6.7, 13.8, 4.2, -0.8, 2.4, -4.6, 
-6.8, 10.9, NA, 4.9, NA, 4, 0.9, -11.4), w5 = c(-0.2, -11.4, 
0.9, -0.8, 8.7, -15.7, 4.2, 4.2, 2.5, -16.4, -13.8, 10.5, 13.8, 
-8.7, 4, -4.6, NA, 14.4, -12.6, -6.8, 13.5, 17.5, NA, -5.7, 10.9, 
-5.6, 4.2, -6.7, 4.9, -5.8, 8.2, -25.9), w6 = c(4.2, 8.7, -6.8, 
4.2, -12.6, 13.5, -15.7, 4, -25.9, -5.6, 10.5, 2.4, 0.9, 2.5, 
4.2, NA, 14.4, -0.8, -13.8, NA, 4.9, -0.2, 17.5, -11.4, 8.2, 
10.3, 13.8, -5.7, -4.6, -8.7, 10.9, -16.4), w7 = c(10.3, -8.7, 
13.5, 10.5, 14.4, 2.4, 0.9, 10.9, -11.4, -4.6, -5.8, -15.7, 4, 
-12.6, 8.2, 17.5, 8.7, -13.8, -5.6, -0.8, 13.8, 4.2, -16.4, NA, 
2.5, -6.7, -5.7, -0.2, -25.9, NA, 4.2, 4.2), w8 = c(4.9, -0.8, 
-12.6, -5.6, -25.9, 4.2, -8.7, 10.3, 4.2, 17.5, 13.5, -5.8, 4.2, 
4, 2.4, -5.7, 10.9, -6.8, 8.7, 14.4, NA, -13.8, 8.2, -16.4, 0.9, 
-0.2, -15.7, NA, -6.7, 10.5, 2.5, 13.8), w9 = c(13.8, NA, 4, 
NA, -5.8, NA, 10.9, -6.8, -16.4, 4.2, NA, NA, 4.9, -11.4, -12.6, 
-5.6, 17.5, 4.2, -0.2, -15.7, 0.9, -6.7, -25.9, 2.5, -8.7, 2.4, 
10.3, -5.7, -4.6, 8.2, NA, 10.5), w10 = c(-5.7, -6.8, 4.2, -6.7, 
4.9, 14.4, 8.2, -12.6, 10.9, 10.3, 2.4, 8.7, NA, NA, 13.8, -13.8, 
-0.8, NA, NA, -4.6, -25.9, 4, -0.2, -15.7, -5.6, NA, -11.4, -5.8, 
-16.4, 13.5, -8.7, NA), w11 = c(-0.8, -15.7, NA, 2.4, 13.5, 10.5, 
-5.8, 2.5, NA, -5.7, -16.4, 4.9, 8.2, 4.2, NA, -25.9, -13.8, 
8.7, 0.9, -12.6, -4.6, NA, 17.5, 14.4, 4.2, 10.3, -6.7, -11.4, 
-0.2, 4.2, 4, -6.8), w12 = c(-25.9, 8.2, -5.8, -5.6, NA, -6.8, 
2.5, 13.5, -11.4, 2.4, 4.2, 10.5, -12.6, 10.9, 0.9, 10.3, -0.2, 
14.4, -0.8, -8.7, 13.8, -13.8, -6.7, 4.2, NA, -5.7, -16.4, 4.2, 
17.5, 8.7, 4.9, -4.6), w13 = c(13.5, -16.4, 17.5, 8.2, 10.5, 
-0.8, -6.8, -13.8, 4.9, -6.7, 8.7, 4.2, 4.2, 4.2, -11.4, -0.2, 
-5.6, -15.7, 14.4, 4, 10.9, 2.4, -5.7, 13.8, -5.8, -8.7, -4.6, 
-25.9, 10.3, -12.6, 2.5, 0.9), w14 = c(-6.7, 14.4, 2.4, -0.2, 
-5.8, 13.8, 4, 0.9, 8.7, -13.8, -6.8, 13.5, 10.9, 8.2, 2.5, -16.4, 
-8.7, -5.6, 17.5, -15.7, 4.2, 10.5, -4.6, -25.9, -12.6, 4.2, 
4.9, 10.3, 4.2, -0.8, -11.4, -5.7), w15 = c(-5.7, 4, 10.9, 14.4, 
-6.8, -5.8, 8.2, -12.6, 4.9, 17.5, 10.5, -13.8, 0.9, 2.5, -8.7, 
10.3, 4.2, -0.8, 2.4, 8.7, 4.2, 4.2, -6.7, 13.8, 13.5, -0.2, 
-4.6, -25.9, -16.4, -15.7, -5.6, -11.4), w16 = c(-25.9, -5.8, 
2.5, 10.3, 8.2, -0.8, -0.2, -15.7, 0.9, -12.6, 8.7, -6.8, -8.7, 
13.8, 4.2, 4, 10.5, 2.4, -5.6, 13.5, -5.7, 4.2, -13.8, 4.2, -6.7, 
-4.6, -16.4, 17.5, -11.4, 14.4, 10.9, 4.9), w17 = c(-4.6, -15.7, 
8.2, 4.2, 13.5, 10.5, 4, -8.7, 4.2, 10.3, 14.4, -0.8, 10.9, 4.2, 
2.5, 17.5, -5.6, 8.7, -13.8, -6.8, 4.9, 2.4, -0.2, -11.4, -12.6, 
-6.7, -5.7, -16.4, -25.9, -5.8, 0.9, 13.8)), .Names = c("team", 
"w1", "w2", "w3", "w4", "w5", "w6", "w7", "w8", "w9", "w10", 
"w11", "w12", "w13", "w14", "w15", "w16", "w17"), row.names = c(NA, 
32L), class = "data.frame")

And here's the code so far:

require(dplyr)
require(ggvis)
require(tidyr) # For the gather function

df2 <- df %>% gather(key, value, w1:w17)
names(df2) <- c("team", "week", "defense")

df2 %>% 
  ggvis(~week, ~team, fill = ~defense) %>%
  layer_rects(width = band(), height = band()) %>%
  scale_nominal("x", padding = 0, points = FALSE) %>%
  scale_nominal("y", padding = 0, points = FALSE)

回答1:

I set the colors for each cell by creating a new variable def.color that maps each value of defense to a specific color. In ggplot2 you can set the colors directly within the call to ggplot using one line of code, such as scale_fill_manual(), rather than adding a color variable to the data frame. I'm hoping there's a way to do that in ggvis, but I haven't found it yet. So, for now, here we go:

# Create a new variable df2$def.color for mapping df2$defense values to colors

# Functions to create color ramps for the blue and orange color ranges
Blue = colorRampPalette(c("darkblue","lightblue"))
Orange = colorRampPalette(c("orange","darkorange3"))

# Negative values of defense get a blue color scale with 10 colors
df2$def.color[!is.na(df2$defense) & df2$defense<0] = 
  as.character(cut(df2$defense[!is.na(df2$defense) & df2$defense<0], 
                   seq(min(df2$defense - 0.1, na.rm=TRUE), 0, length.out=11), 
                   labels=Blue(10)))

# Positive values of defense get an orange color scale with 10 colors
df2$def.color[!is.na(df2$defense) & df2$defense>=0] = 
  as.character(cut(df2$defense[!is.na(df2$defense) & df2$defense>=0], 
                   seq(0, max(df2$defense, na.rm=TRUE)+0.1, length.out=11), 
                   labels=Orange(10)))

# Set NA values in df2$def.color to light gray in df2$def.color
df2$def.color[is.na(df2$defense)] = "#E5E5E5"  

# Set NA values in df2$defense to blanks so that we won't get "NaN" in cells with 
# missing data
df2$defense[is.na(df2$defense)] = ""

Now we create the plot. To get the colors, map def.color to fill using := to override the default colors. To add the values of defense use layer_text. I'm not happy with the text placement within each cell, but this is the best I've been able to come up with for now.

df2 %>% 
  ggvis(~week, ~team, fill:=~def.color) %>% 
  layer_rects(width = band(), height = band()) %>%
  scale_nominal("x", padding = 0, points = FALSE) %>%
  scale_nominal("y", padding = 0, points = FALSE) %>%
  layer_text(text:=~defense, stroke:="white", align:="left", baseline:="top") 



回答2:

A solution I found to display a legend, making use of the scale_ordinal function. I used a lot of the code written by @eipi10, thank you!

# Functions to create color ramps for the blue and orange color ranges,
# combined in a single palette with 10 colors of each ramp and gray for NAs
Blue <- colorRampPalette(c("darkblue","lightblue"))
Orange <- colorRampPalette(c("orange","darkorange3"))
palette <- c(Blue(10), "#E5E5E5", Orange(10))

# Negative values of defense get a blue color scale with 10 colors, indexes
# from 1 to 10
df2$def.label[!is.na(df2$defense) & df2$defense<0] <- 
    as.character(cut(df2$defense[!is.na(df2$defense) & df2$defense<0], 
                 seq(min(df2$defense - 0.1, na.rm = TRUE), 0, length.out = 11), 
                 labels = palette[1:10]))

# Positive values of defense get an orange color scale with 10 colors,
# indexes from 12 to 21
df2$def.label[!is.na(df2$defense) & df2$defense>=0] <-
    as.character(cut(df2$defense[!is.na(df2$defense) & df2$defense>=0], 
                 seq(0, max(df2$defense, na.rm = TRUE) + 0.1, length.out = 11), 
                 labels = palette[12:21]))

# Set NA values in df2$defense to 11 in def.label, the label for gray color
df2$def.label[is.na(df2$defense)] <- palette[[11]] 

# Define the values to be displayed on the legend
pos.cut.values <- seq(0, max(df2$defense, na.rm = TRUE) + 0.1, length.out = 11)
neg.cut.values <- seq(min(df2$defense - 0.1, na.rm = TRUE), 0, length.out = 11)
legend.values <- c(paste(neg.cut.values[1:10], '..', neg.cut.values[2:11]),
                   'NA', paste(pos.cut.values[1:10], '..', pos.cut.values[2:11]))

# Set NA values in df2$defense to blanks so that we won't get "NaN" in cells
# with missing data
df2$defense[is.na(df2$defense)] <- ""

df2 %>% 
    ggvis(~week, ~team, fill:=~def.label) %>% 
    scale_ordinal('fill', range = palette) %>%
    add_legend(scales = 'fill', values = legend.values) %>%
    layer_rects(width = band(), height = band()) %>%
    scale_nominal("x", padding = 0, points = FALSE) %>%
    scale_nominal("y", padding = 0, points = FALSE) %>%
    layer_text(text := ~defense, stroke := "white", align := "left",
               baseline := "top") 



标签: r ggvis