suppressPackageStartupMessages(library(tidyverse))
library(gt)

Grab the the most recent advanced metrics from basketball reference using the {nbastatR} package by Alex Bresler. Note, running bref_players_stats() will assign the output data frames, dataBREFPlayerTotals and dataBREFPlayerAdvanced, to the environment, so we don’t need to do anything else (I rename them for my own sanity).

library(nbastatR)
bref_players_stats(seasons = 2019, tables = c("advanced", "totals"), widen = TRUE, assign_to_environment = TRUE)
bref_advanced <- dataBREFPlayerAdvanced
bref_totals <- dataBREFPlayerTotals

Now we can filter and munge as needed:

adv_player_stats <- bref_advanced %>%
  filter(minutes >= 500) %>%
  mutate(bref_url = glue::glue("https://www.basketball-reference.com/players/{stringr::str_sub(idPlayer, 1, 1)}/{idPlayer}.html"),
         bref_link = glue::glue('<a href="{bref_url}">{namePlayer}</a>'))

Collapse positions into front and backcourt:

unique_positions <- unique(bref_advanced$idPosition)
frontcourt <- c("PF", "SF", "C", "PF-SF", "C-PF", "SG-PF", "SF-PF")
backcourt <- c("PG", "SG", "PG-SG", "SG-PG", "SF-SG", "SG-SF")

bref_efg <- bref_totals %>%
  select(one_of(c("idPlayer", "pctEFG")))

adv_player_stats <- adv_player_stats %>%
  left_join(bref_efg, by = "idPlayer") %>%
  mutate( "position" = case_when(
    idPosition %in% frontcourt ~ "frontcourt",
    idPosition %in% backcourt ~ "backcourt",
    TRUE ~ "other"),
    "position" = as.factor(position)
  )

Let’s also get some info from the NBA Stats API using teams_players_states(). By using assign_to_environment = TRUE, we’ll automatically get a data frame dataGeneralPlayers. For now I just want players’ offensive rating1, ortg, and defensive rating2, drtg.

nbastatR::teams_players_stats(seasons = 2019, types = c("player"), tables = "general", measures = "Advanced", assign_to_environment = TRUE)
player_rtgs <- dataGeneralPlayers %>%
  select(one_of(c("idPlayer", "ortg", "drtg")))

adv_player_stats <- adv_player_stats %>%
  left_join(player_rtgs, by = c("idPlayerNBA" = "idPlayer"))
adv_player_stats %>%
  ggplot(aes(x = ratioPER)) +
  geom_histogram(alpha = 0.7, fill = "#011627") +
  labs(title = "PER for players with 500+ minutes",
       subtitle = "NBA 2018-2019 season",
       caption = glue::glue("data via nbastatR {yesterday}")) +
  hrbrthemes::theme_ipsum_rc()

adv_player_stats %>%
  ggplot(aes(x = ratioVORP)) +
  geom_histogram(alpha = 0.7, fill = "#011627") +
  labs(title = "Value Over Replacement Player (VORP)",
       subtitle = "NBA 2018-2019 season, players with 500+ minutes",
       caption = glue::glue("data via nbastatR {yesterday}")) +
  hrbrthemes::theme_ipsum_rc()

adv_player_stats %>%
  ggplot(aes(x = ratioWS)) +
  geom_histogram(alpha = 0.7, fill = "#011627") +
  labs(title = "Win Shares for players with 500+ minutes",
       subtitle = "NBA 2018-2019 season",
       caption = glue::glue("data via nbastatR {yesterday}")) +
  hrbrthemes::theme_ipsum_rc()

adv_player_stats %>%
  ggplot(aes(x = ratioOBPM, y = ratioDBPM)) +
  geom_point() +
  geom_hline(yintercept = 0, alpha = 0.6, lty = "dashed") +
  geom_vline(xintercept = 0, alpha = 0.6, lty = "dashed") +
  labs(title = "Offensive vs. Defensive Box Plus-Minus",
       subtitle = glue::glue("NBA 2018-2019 season through {yesterday}"),
       caption = glue::glue("data via nbastatR"),
       x = "OBPM",
       y = "DBPM") +
  hrbrthemes::theme_ipsum_rc()

Make a little helper function to get the top 10 players for some variable.

get_top10 <- function(df, column) {
  require(rlang)
  column <- enquo(column)
  dplyr::top_n(df, n = 10, wt = !!column) %>%
    pull(namePlayer)
}
# get top 10 for desired variable (in this case ratioBPM)
top10_BPM <- top_n(adv_player_stats, n = 10, wt = ratioBPM) %>%
  pull(namePlayer)

adv_player_stats %>%
  ggplot(aes(x = ratioOBPM, y = ratioDBPM)) +
  geom_point(color = "#011627") +
  gghighlight::gghighlight(namePlayer %in% top10_BPM, label_key = namePlayer,
                           label_params = list(fill = ggplot2::alpha("white", 0.8),
                                                 box.padding = 0,
                                                 family = "Roboto Condensed"),
                           unhighlighted_colour = "#007190") +
  geom_hline(yintercept = 0, alpha = 0.6, lty = "dashed") +
  geom_vline(xintercept = 0, alpha = 0.6, lty = "dashed") +
  labs(title = "Offensive vs. Defensive Box Plus-Minus: Top 10 Box Plus/Minus",
       subtitle = glue::glue("NBA 2018-2019 season through {yesterday}"),
       caption = glue::glue("data via nbastatR"),
       x = "OBPM",
       y = "DBPM") +
  hrbrthemes::theme_ipsum_rc()

top10_WS <- get_top10(adv_player_stats, ratioWSPer48)
#> Loading required package: rlang
#> 
#> Attaching package: 'rlang'
#> The following objects are masked from 'package:purrr':
#> 
#>     %@%, as_function, flatten, flatten_chr, flatten_dbl,
#>     flatten_int, flatten_lgl, flatten_raw, invoke, list_along,
#>     modify, prepend, splice

adv_player_stats %>%
  ggplot(aes(x = ratioOWS, y = ratioDWS)) +
  geom_point(color = "#011627") +
  gghighlight::gghighlight(namePlayer %in% top10_WS, label_key = namePlayer,
                           label_params = list(fill = ggplot2::alpha("white", 0.8),
                                                 box.padding = 0,
                                                 family = "Roboto Condensed"),
                           unhighlighted_colour = "#007190") +
  geom_hline(yintercept = 0, alpha = 0.6, lty = "dashed") +
  geom_vline(xintercept = 0, alpha = 0.6, lty = "dashed") +
  labs(title = "Offensive vs. Defensive Win Shares: Top 10 WS Per 48",
       subtitle = glue::glue("NBA 2018-2019 season through {yesterday}"),
       caption = glue::glue("data via nbastatR"),
       x = "OWS",
       y = "DWS") +
  hrbrthemes::theme_ipsum_rc()

top10_EFG <- get_top10(adv_player_stats, pctEFG)

adv_player_stats %>%
  ggplot(aes(x = (pctTrueShooting - mean(pctTrueShooting)), y = (ratioPER - mean(ratioPER)))) +
  geom_point(color = "#011627") +
  gghighlight::gghighlight(namePlayer %in% top10_EFG, label_key = namePlayer,
                           label_params = list(fill = ggplot2::alpha("white", 0.8),
                                                 box.padding = 0,
                                                 family = "Roboto Condensed"),
                           unhighlighted_colour = "#007190") +
  geom_hline(yintercept = 0, alpha = 0.6, lty = "dashed") +
  geom_vline(xintercept = 0, alpha = 0.6, lty = "dashed") +
  labs(title = "TS% above avg vs. PER above avg: Top 10 EFG%",
       subtitle = glue::glue("NBA 2018-2019 season"),
       caption = glue::glue("data via nbastatR, {yesterday}"),
       x = "true shooting %",
       y = "player efficiency rating") +
  hrbrthemes::theme_ipsum_rc()

Box Plus/Minus Tables

print_glyphs <- unescape_html(str_c(glyphs, sep = ",", collapse = ", "))

Because I super love playing with Rich Iannone’s {gt}:

adv_player_stats %>%
  select(namePlayer, idPosition, ratioBPM, ratioOBPM, ratioDBPM, bref_url, urlPlayerHeadshot) %>%
  arrange(desc(ratioBPM)) %>%
  top_n(n = 10, wt = ratioBPM) %>%
  gt::gt(rowname_col = "namePlayer") %>%
  tab_header(
    title = md("**Top 10 Box Plus/Minus**")
  ) %>%
  cols_label(
    idPosition = md("**Position**"),
    ratioBPM = md("**BPM**"),
    ratioOBPM = md("**OBPM**"),
    ratioDBPM = md("**DBPM**"),
    bref_url = md("**Link**"),
    urlPlayerHeadshot = md("")
  ) %>%
  text_transform(
    locations = cells_data(vars(bref_url)),
    fn = function(x) {
      sprintf("<a href=%s>profile</a>", x)
    }
  ) %>%
    text_transform(
    locations = cells_data(vars(urlPlayerHeadshot)),
    fn = function(x) {
      web_image(url = x) 
    }
  ) %>%
  tab_source_note(
    md("source: [basketball-reference.com](https://www.basketball-reference.com) via [nbastatR](http://asbcllc.com/nbastatR/index.html)")
  ) %>%
  tab_footnote(
    footnote = ("Players with 500+ minutes."),
    locations = cells_title("title")
    ) %>%
  tab_footnote(
    footnote = ("Box Plus/Minus: a box score estimate of the points per 100 possessions that a player contributed above a league-average player, translated to an average team."),
    locations = cells_column_labels(
      columns = vars(ratioBPM)
    )
  ) %>%
  tab_footnote(
    footnote = ("Offensive Box Plus/Minus."),
    locations = cells_column_labels(
      columns = vars(ratioOBPM)
    )
  ) %>%
  tab_footnote(
    footnote = ("Defensive Box Plus/Minus."),
    locations = cells_column_labels(
      columns = vars(ratioDBPM)
    )
  ) %>%
  tab_options(footnote.glyph = c(print_glyphs),
              table.width = px(640))
Top 10 Box Plus/Minus*
Position BPM OBPM DBPM § Link
James Harden PG 11.7 10.6 1.1 profile
Giannis Antetokounmpo PF 10.8 5.7 5.1 profile
Nikola Jokic C 9.6 5.8 3.8 profile
Anthony Davis C 8.5 4.7 3.9 profile
LeBron James SF 8.1 6.2 1.9 profile
Rudy Gobert C 7.1 2.0 5.1 profile
Karl-Anthony Towns C 6.8 4.8 1.9 profile
Kyrie Irving PG 6.4 6.0 0.3 profile
Nikola Vucevic C 6.4 3.0 3.4 profile
Russell Westbrook PG 6.4 2.5 3.8 profile
source: basketball-reference.com via nbastatR
* Players with 500+ minutes.
Box Plus/Minus: a box score estimate of the points per 100 possessions that a player contributed above a league-average player, translated to an average team.
Offensive Box Plus/Minus.
§ Defensive Box Plus/Minus.


adv_player_stats %>%
  select(urlPlayerHeadshot, namePlayer, idPosition, ratioBPM, ratioOBPM, ratioDBPM) %>%
  arrange(desc(ratioOBPM)) %>%
  top_n(n = 10, wt = ratioOBPM) %>%
  gt::gt() %>%
  tab_header(
    title = md("**Top 10 Offensive Box Plus/Minus**")
  ) %>%
  cols_label(
    namePlayer = md("**Player**"),
    urlPlayerHeadshot = md(""),
    idPosition = md("**Position**"),
    ratioBPM = md("**BPM**"),
    ratioOBPM = md("**OBPM**"),
    ratioDBPM = md("**DBPM**")
  ) %>%
    text_transform(
    locations = cells_data(vars(urlPlayerHeadshot)),
    fn = function(x) {
      web_image(url = x) 
    }
  ) %>%
  tab_source_note(
    md("source: [basketball-reference.com](https://www.basketball-reference.com) via [nbastatR](http://asbcllc.com/nbastatR/index.html)")
  ) %>%
  tab_footnote(
    footnote = ("Players with 500+ minutes."),
    locations = cells_title("title")
  ) %>%
  tab_footnote(
    footnote = ("Box Plus/Minus; a box score estimate of the points per 100 possessions that a player contributed above a league-average player, translated to an average team."),
    locations = cells_column_labels(
      columns = vars(ratioBPM)
    )
  ) %>%
  tab_footnote(
    footnote = ("Offensive Box Plus/Minus."),
    locations = cells_column_labels(
      columns = vars(ratioOBPM)
    )
  ) %>%
  tab_footnote(
    footnote = ("Defensive Box Plus/Minus."),
    locations = cells_column_labels(
      columns = vars(ratioDBPM)
    )
  ) %>%
  tab_options(footnote.glyph = c(print_glyphs),
              table.width = px(640))
Top 10 Offensive Box Plus/Minus*
Player Position BPM OBPM DBPM §
James Harden PG 11.7 10.6 1.1
Stephen Curry PG 6.3 7.7 -1.4
Damian Lillard PG 5.6 6.6 -1.0
LeBron James SF 8.1 6.2 1.9
Kyrie Irving PG 6.4 6.0 0.3
Nikola Jokic C 9.6 5.8 3.8
Giannis Antetokounmpo PF 10.8 5.7 5.1
Kemba Walker PG 3.3 4.9 -1.6
Karl-Anthony Towns C 6.8 4.8 1.9
Mike Conley PG 3.4 4.8 -1.3
Paul George SF 5.4 4.8 0.7
source: basketball-reference.com via nbastatR
* Players with 500+ minutes.
Box Plus/Minus; a box score estimate of the points per 100 possessions that a player contributed above a league-average player, translated to an average team.
Offensive Box Plus/Minus.
§ Defensive Box Plus/Minus.


adv_player_stats %>%
  select(urlPlayerHeadshot, namePlayer, idPosition, ratioBPM, ratioOBPM, ratioDBPM) %>%
  arrange(desc(ratioDBPM)) %>%
  top_n(n = 10, wt = ratioDBPM) %>%
  gt::gt() %>%
  tab_header(
    title = md("**Top 10 Defensive Box Plus/Minus**")
  ) %>%
  cols_label(
    namePlayer = md("**Player**"),
    urlPlayerHeadshot = md(""),
    idPosition = md("**Position**"),
    ratioBPM = md("**BPM**"),
    ratioOBPM = md("**OBPM**"),
    ratioDBPM = md("**DBPM**")
  ) %>%
    text_transform(
    locations = cells_data(vars(urlPlayerHeadshot)),
    fn = function(x) {
      web_image(url = x) 
    }
  ) %>%
  tab_source_note(
    md("source: [basketball-reference.com](https://www.basketball-reference.com) via [nbastatR](http://asbcllc.com/nbastatR/index.html)")
  ) %>%
  tab_footnote(
    footnote = ("Players with 500+ minutes."),
    locations = cells_title("title")
  ) %>%
  tab_footnote(
    footnote = ("Box Plus/Minus; a box score estimate of the points per 100 possessions that a player contributed above a league-average player, translated to an average team."),
    locations = cells_column_labels(
      columns = vars(ratioBPM)
    )
  ) %>%
  tab_footnote(
    footnote = ("Offensive Box Plus/Minus."),
    locations = cells_column_labels(
      columns = vars(ratioOBPM)
    )
  ) %>%
  tab_footnote(
    footnote = ("Defensive Box Plus/Minus."),
    locations = cells_column_labels(
      columns = vars(ratioDBPM)
    )
  ) %>%
  tab_options(footnote.glyph = c(print_glyphs),
              table.width = px(640))
Top 10 Defensive Box Plus/Minus*
Player Position BPM OBPM DBPM §
Nerlens Noel C 3.8 -1.6 5.4
Mitchell Robinson C 5.9 0.6 5.3
Giannis Antetokounmpo PF 10.8 5.7 5.1
Rudy Gobert C 7.1 2.0 5.1
Myles Turner C 3.3 -1.4 4.7
Anthony Davis C 8.5 4.7 3.9
Mason Plumlee C 3.8 -0.1 3.9
Nikola Jokic C 9.6 5.8 3.8
Russell Westbrook PG 6.4 2.5 3.8
Bam Adebayo C 3.1 -0.5 3.6
Jusuf Nurkic C 5.2 1.5 3.6
source: basketball-reference.com via nbastatR
* Players with 500+ minutes.
Box Plus/Minus; a box score estimate of the points per 100 possessions that a player contributed above a league-average player, translated to an average team.
Offensive Box Plus/Minus.
§ Defensive Box Plus/Minus.

Highcharts

Messing around with highcharts courtesy of Joshua Kunst’s {highcharter} package.

library(highcharter)
#> Highcharts (www.highcharts.com) is a Highsoft software product which is
#> not free for commercial and Governmental use
hchart(adv_player_stats, "scatter", hcaes(x = "ratioOBPM", y = "ratioDBPM", group = "position", name = "namePlayer", OBPM = "ratioOBPM", DBPM = "ratioDBPM", position = "position")) %>%
  hc_tooltip(pointFormat = "<b>{point.name}</b><br />OBPM: {point.OBPM}<br />DBPM: {point.DBPM}") %>%
  hc_title(text = "Offensive vs. Defensive Box Plus/Minus") %>%
  hc_subtitle(text = "NBA 2018-2019 Season") %>%
  hc_credits(enabled = TRUE,
             text = "data via nbastatR",
             style = list(
               fontSize = "10px"
               )
             ) %>%
  hc_add_theme(hc_theme_538())
hchart(adv_player_stats, "scatter", hcaes(x = "ratioOWS", y = "ratioDWS", group = "position", name = "namePlayer", OWS = "ratioOWS", DWS = "ratioDWS", position = "position")) %>%
  hc_tooltip(pointFormat = "<b>{point.name}</b><br />OWS: {point.OWS}<br />DWS: {point.DWS}") %>%
  hc_title(text = "Offensive vs. Defensive Win Shares") %>%
  hc_subtitle(text = "NBA 2018-2019 Season") %>%
  hc_credits(enabled = TRUE,
             text = "data via nbastatR",
             style = list(
               fontSize = "10px"
               )
             ) %>%
  hc_add_theme(hc_theme_economist())
hchart(adv_player_stats, "scatter", 
       hcaes(x = "pctTrueShooting", y = "ratioPER",
             name = "namePlayer", TS = "pctTrueShooting", 
             PER = "ratioPER", position = "position")) %>%
  hc_tooltip(pointFormat = "<b>{point.name}</b><br />TS%: {point.TS}<br />PER: {point.PER}<br />Position: {point.position}") %>%
  hc_title(text = "True Shooting % vs Player Efficiency Rating") %>%
  hc_subtitle(text = "NBA 2018-2019 Season") %>%
  hc_credits(enabled = TRUE,
             text = "data via nbastatR",
             style = list(
               fontSize = "14px"
               )
             ) %>%
  hc_add_theme(hc_theme_chalk(
    plotOptions = list(
      scatter = list(
        marker = list(radius = 4,
                      fillOpacity = 0.3) # actually this does nothing
        )
      )
    )
  )
hc <- hchart(adv_player_stats, "scatter", hcaes(x = "ratioOWS", y = "ratioDWS", group = "position", name = "namePlayer", OWS = "ratioOWS", DWS = "ratioDWS", Position = "position")) %>%
  hc_tooltip(pointFormat = "<b>{point.name}</b><br />OWS: {point.OWS}<br />DWS: {point.DWS}") %>%
  hc_title(text = "Offensive vs. Defensive Win Shares") %>%
  hc_subtitle(text = "NBA 2018-2019 Season") %>%
  hc_credits(enabled = TRUE,
             text = "by @dataandme data via nbastatR",
             href = "https://github.com/abresler/nbastatR",
             style = list(
               fontSize = "10px",
               color = "#4a4a4a"
               )
             ) 
hc2 <- hchart(adv_player_stats, "scatter", 
       hcaes(x = "ortg", y = "drtg", group = "position",
             name = "namePlayer", ortg = "ortg", 
             drtg = "drtg", position = "position")) %>%
  hc_tooltip(pointFormat = "<b>{point.name}</b><br />ORTG: {point.ortg}<br />DRTG: {point.drtg}<br />Position: {point.position}") %>%
  hc_title(text = "Offensive vs. Defensive Rating") %>%
  hc_subtitle(text = "NBA 2018-2019 Season") %>%
  hc_credits(enabled = TRUE,
             text = "data via nbastatR",
             style = list(
               fontSize = "14px"
               )
             )

Playing with palettes and themeing…

Here’s a figure that Highcharts had in its documentation that I very much wish I’d found before I started mucking about with making my own themes.

Highcharts: Design and Style - Chart positioning

Highcharts: Design and Style - Chart positioning

hc %>%
  hc_add_theme(hrbrish)
hc2 %>%
  hc_add_theme(hc_theme_bloom())

Getting pomological 🍅

Add pomological palettes from Garrick Aden-Buie’s {ggpomological} package:

# source: https://github.com/gadenbuie/ggpomological/blob/master/R/scale_pomological.R
pomological_palette <- c(
   "#c03728" #red
  ,"#919c4c" #green darkish
  ,"#fd8f24" #orange brighter
  ,"#f5c04a" #yelloww
  ,"#e68c7c" #pink
  ,"#828585" #light grey
  ,"#c3c377" #green light
  ,"#4f5157" #darker blue/grey
  ,"#6f5438" #lighter brown
)

pomological_base <- list(
  "paper"       = "#fffeea",
  "paper_alt"   = "#f8eed1",
  "light_line"  = "#efe1c6",
  "medium_line" = "#a89985",
  "darker_line" = "#6b452b",
  "black"       = "#3a3e3f",
  "dark_blue"   = "#2b323f"
)

#' Pomological Color and Fill Scales
#' 
#' Color scales based on the USDA Pomological Watercolors paintings.
#' 
#' @references https://usdawatercolors.nal.usda.gov/pom
#' @seealso [ggplot2::scale_colour_discrete] [ggplot2::scale_fill_discrete]
#' @inheritDotParams ggplot2::discrete_scale
#' @name scale_pomological
NULL
#> NULL

pomological_pal <- function() scales::manual_pal(pomological_palette)

#' @rdname scale_pomological
#' @export
scale_colour_pomological <- function(...) ggplot2::discrete_scale("colour", "pomological", pomological_pal(), ...)

#' @rdname scale_pomological
#' @export
scale_color_pomological <- scale_colour_pomological

#' @rdname scale_pomological
#' @export
scale_fill_pomological <- function(...) ggplot2::discrete_scale('fill', 'pomological', pomological_pal(), ...)
#' Olden timey theme for highcharts
#' 
#' @param ... Named argument to modify the theme
#' 
#' @examples
#' 
#' highcharts_demo() %>% 
#'   hc_add_theme(hc_theme_oldentimey())
#' 
#' @importFrom grDevices colorRampPalette 
#' @export
hc_theme_oldentimey <- function(...){
  
  theme <-   
  list(
    colors = pomological_palette,
    chart = list(
      divBackgroundImage = "https://raw.githubusercontent.com/gadenbuie/ggpomological/master/inst/images/pomological_background.png",
      backgroundColor = "transparent",
      plotBorderColor = pomological_base$paper,
      colorAxis = list(
        gridLineColor = pomological_base$darker_line
      ),
      style = list(
        fontFamily = "Homemade Apple",
        color = pomological_base$dark_blue
      )
    ),
    plotOptions = list(
      scatter = list(
        marker = list(
          radius = 4
        )
      )
    ),
    title = list(
      style = list(
        fontSize = "22px",
        color = pomological_base$dark_blue
      )
    ),
    subtitle = list(
      style = list(
        fontSize = "18px",
        color = pomological_base$dark_blue
      )
    ),
    legend = list(
      enabled = TRUE,
      itemStyle = list(
        fontSize = "14px",
        fontWeight = "light",
        color = pomological_base$dark_blue
      )
    ),
   credits = list(
    enabled = TRUE,
    position = list(
      x = -15,  # highcharts default: -10
      y = -10   # highchart default: -5
    ),
    style = list(
      fontFamily = "Mr De Haviland",
      fontSize = "18px",
      color = pomological_base$dark_blue,
      fontWeight = "light"
    ),
    xAxis = list(
      lineWidth = 1,
      tickWidth = 1,
      gridLineColor = "transparent",
      labels = list(
        enabled = TRUE,
        style = list(
          color = pomological_base$dark_blue,
          fontSize = "18px"
        )
      ),
      # x-axis title
      title = list(
        enabled = TRUE,
        style = list(
          color = pomological_base$dark_blue,
          fontSize = "18px"
          
        )
      )
    ),
    yAxis = list(
      lineWidth = 1,
      tickWidth = 1,
      gridLineColor = "transparent",
      labels = list(
        enabled = TRUE,
        style = list(
          color = pomological_base$dark_blue,
          fontSize = "18px"
        )
      ),
      # y-axis title
      title = list(
        enabled = TRUE,
        style = list(
          color = pomological_base$dark_blue,
          fontSize = "18px"
          
        )
      )
    ),
    tooltip = list(
      backgroundColor = "#f8eed1",
      style = list(
        color = pomological_base$dark_blue,
        fontSize = "18px",
        padding = "10px"
      )
    )
  ))
  
  theme <- structure(theme, class = "hc_theme")
  
  if (length(list(...)) > 0) {
    theme <- hc_theme_merge(
      theme,
      hc_theme(...)
    )
  } 
  
  theme
}
hc2 %>%
  hc_add_theme(hc_theme_oldentimey())

Since the scattered points don’t take an alpha param, let’s see if we can make things work using rgba colours (in this example we’ll set opacity to 70%)3:

pom_pal_70 <- c(
  "rgba(192, 55, 40, 0.7)", # red
  "rgba(145, 156, 76, 0.7)", # green darkish
  "rgba(253, 143, 36, 0.7)", # orange brighter
  "rgba(245, 192, 74, 0.7)", # yellow
  "rgba(230, 140, 124, 0.7)", # pink
  "rgba(130, 133, 133, 0.7)", # light grey
  "rgba(195, 195, 119, 0.7)", # green light
  "rgba(79, 81, 87, 0.7)", # darker blue/grey
  "rgba(111, 84, 56, 0.7)" # lighter brown
)

Note: this could easily be a function where you pass in the alpha as a parameter and modify an rgb() color to become an rgba() one with the appropriate setting.

Actually, turns out there’s a function that would’ve basically done this for me… You can start off with Garrick’s pomological_palette, and then use col2rgb() to convert the colours appropriately.

pomological_palette <- c(
   "#c03728" #red
  ,"#919c4c" #green darkish
  ,"#fd8f24" #orange brighter
  ,"#f5c04a" #yelloww
  ,"#e68c7c" #pink
  ,"#828585" #light grey
  ,"#c3c377" #green light
  ,"#4f5157" #darker blue/grey
  ,"#6f5438" #lighter brown
)

rgb_pom_pal <- as_tibble(grDevices::col2rgb(pomological_palette), .name_repair = "universal")
#> New names:
#> * `` -> ...1
#> * `` -> ...2
#> * `` -> ...3
#> * `` -> ...4
#> * `` -> ...5
#> * … and 4 more problems

rgb_pom_pal <- as.data.frame(rgb_pom_pal)


rownames(rgb_pom_pal) <- c("red", "green", "blue")

rgb_pom_pal <- rgb_pom_pal %>% 
  rownames_to_column()

Just one minor problem…the shape.

rgb_pom_pal <- rgb_pom_pal %>%
  gather(color, measure, ...1:...9)

# note, obviously you could dynamically deal with opacity, 
# and not just hard-code it...
rgb_pom_pal <- rgb_pom_pal %>% 
  spread(rowname, measure) %>%
  select(one_of(c("color", "red", "green", "blue"))) %>%
  mutate("rgb" = glue::glue("rgb({red}, {green}, {blue})"),
         "rgba" = glue::glue("rgba({red}, {green}, {blue}, 0.8)"))

rgb_pom_pal
#>   color red green blue                rgb                     rgba
#> 1  ...1 192    55   40   rgb(192, 55, 40)   rgba(192, 55, 40, 0.8)
#> 2  ...2 145   156   76  rgb(145, 156, 76)  rgba(145, 156, 76, 0.8)
#> 3  ...3 253   143   36  rgb(253, 143, 36)  rgba(253, 143, 36, 0.8)
#> 4  ...4 245   192   74  rgb(245, 192, 74)  rgba(245, 192, 74, 0.8)
#> 5  ...5 230   140  124 rgb(230, 140, 124) rgba(230, 140, 124, 0.8)
#> 6  ...6 130   133  133 rgb(130, 133, 133) rgba(130, 133, 133, 0.8)
#> 7  ...7 195   195  119 rgb(195, 195, 119) rgba(195, 195, 119, 0.8)
#> 8  ...8  79    81   87    rgb(79, 81, 87)    rgba(79, 81, 87, 0.8)
#> 9  ...9 111    84   56   rgb(111, 84, 56)   rgba(111, 84, 56, 0.8)

After all of this, I discovered there’s actually a function, plotly::toRGB(), which deals with the rgb matrix from grDevices:col2rgb(), and outputs in the format "rgba(70,130,180,1)". So, in the end that’s probably the best bet.

plotly::toRGB(x = "red", alpha = 0.8)
#> [1] "rgba(255,0,0,0.8)"
plotly::toRGB(x = "#c03728", alpha = 0.8)
#> [1] "rgba(192,55,40,0.8)"

All of that code above could’ve basically been:

rgba_pomological_pal <- plotly::toRGB(pomological_palette, alpha = 0.8)
hc_theme_oldentimey_alpha <- function(...){
  
  theme <-   
  list(
    colors = rgba_pomological_pal,
    chart = list(
      divBackgroundImage = "https://raw.githubusercontent.com/gadenbuie/ggpomological/master/inst/images/pomological_background.png",
      spacingTop = 30,
      backgroundColor = "transparent",
      plotBorderColor = pomological_base$paper,
      colorAxis = list(
        gridLineColor = pomological_base$darker_line
      ),
      style = list(
        fontFamily = "Homemade Apple",
        color = pomological_base$dark_blue
      )
    ),
    plotOptions = list(
      scatter = list(
        marker = list(
          radius = 4
        )
      )
    ),
    title = list(
      style = list(
        fontSize = "22px",
        color = pomological_base$dark_blue
      )
    ),
    subtitle = list(
      style = list(
        fontSize = "18px",
        color = pomological_base$dark_blue
      )
    ),
    legend = list(
      enabled = TRUE,
      itemStyle = list(
        fontSize = "14px",
        fontWeight = "light",
        color = pomological_base$dark_blue
      )
    ),
   credits = list(
    enabled = TRUE,
    position = list(
      x = -15,  # highcharts default: -10
      y = -10   # highchart default: -5
    ),
    style = list(
      fontFamily = "Mr De Haviland",
      fontSize = "18px",
      color = pomological_base$dark_blue,
      fontWeight = "light"
    ),
    xAxis = list(
      lineWidth = 1,
      tickWidth = 1,
      gridLineColor = "transparent",
      labels = list(
        enabled = TRUE,
        style = list(
          color = pomological_base$dark_blue,
          fontSize = "18px"
        )
      ),
      # x-axis title
      title = list(
        enabled = TRUE,
        style = list(
          color = pomological_base$dark_blue,
          fontSize = "18px"
          
        )
      )
    ),
    yAxis = list(
      lineWidth = 1,
      tickWidth = 1,
      gridLineColor = "transparent",
      labels = list(
        enabled = TRUE,
        style = list(
          color = pomological_base$dark_blue,
          fontSize = "18px"
        )
      ),
      # y-axis title
      title = list(
        enabled = TRUE,
        style = list(
          color = pomological_base$dark_blue,
          fontSize = "18px"
          
        )
      )
    ),
    tooltip = list(
      backgroundColor = "#f8eed1",
      style = list(
        color = pomological_base$dark_blue,
        fontSize = "18px",
        padding = "10px"
      )
    )
  ))
  
  theme <- structure(theme, class = "hc_theme")
  
  if (length(list(...)) > 0) {
    theme <- hc_theme_merge(
      theme,
      hc_theme(...)
    )
  } 
  
  theme
}
hc %>%
  hc_add_theme(hc_theme_oldentimey_alpha())

  1. Measures a team’s points scored per 100 possessions. On a player level this statistic is team points scored per 100 possessions while he is on court.

  2. The number of points allowed per 100 possessions by a team. For a player, it is the number of points per 100 possessions that the team allows while that individual player is on the court.

  3. TIL, there’s a function in grDevices called col2rgb() — so, yeah, that’s pretty cool.