This is the code I used to render the following video.
Data compiled by David Lazar (@davidthelazar) and available here. The version used to create the following video is here.
Notes from twitter:
library(tidyverse)
library(gganimate)
library(transformr)
input <- read.csv("~/Desktop/blaseball/20210723_idolBoardData-rawData.csv")
input %>%
select(-c(3:22)) %>%
pivot_longer(cols = 3:22,
names_to = "position",
values_to = "player") %>%
mutate(position = str_remove(position, "X"),
position = str_remove(position, fixed(".1"))) %>%
full_join(input %>%
select(1:22) %>%
pivot_longer(cols = 3:22,
names_to = "position",
values_to = "eDensity") %>%
mutate(position = str_remove(position, "X")),
by = c("timestamp", "strictlyConfidential", "position")) %>%
mutate(position = as.integer(position),
timestamp = str_trunc(timestamp,
width = 19,
side = "right",
ellipsis = ""),
timestamp = str_replace(timestamp, pattern = "T",replacement = " "),
timestamp = strftime(timestamp,
format = "%F %T"),
timestampPOSIX = strptime(timestamp,
format = "%F %T")) %>%
rename(noodle = strictlyConfidential) -> idols
First, I created the static image from which the frames will be extracted.
idols %>%
# These filters are good for testing how it responds to y-axis changes and x-axis dimensions
#filter(timestampPOSIX > strptime("2021-06-25 02:00:01", format = "%F %T") &
# timestampPOSIX < strptime("2021-06-29 02:00:01", format = "%F %T")) %>%
mutate(timestamp_fct = as.factor(timestamp),
position_fct = as.factor(position)) %>%
group_by(timestampPOSIX) %>%
summarise(total = sum(eDensity)) %>%
full_join(idols %>%
# Same as above.
#filter(timestampPOSIX > strptime("2021-06-25 02:00:01", format = "%F %T") &
# timestampPOSIX < strptime("2021-06-29 02:00:01", format = "%F %T")) %>%
mutate(timestamp_fct = as.factor(timestamp),
position_fct = as.factor(position)),
by = "timestampPOSIX") %>%
# Just to keep the names consistent across all timepoints
mutate(player_name = case_when(player == "--at-ema -lem-f-yo" ~ "Anathema Elemefayo",
player == "B-by Do-le" ~ "Baby Doyle",
player == "Com-issioner V-por" ~ "Commissioner Vapor",
player == "Commissioner V-por" ~ "Commissioner Vapor",
player == "-o- Mit-hel-" ~ "Don Mitchell",
player == "-o- Mitchel-" ~ "Don Mitchell",
player == "-o- Mitchell" ~ "Don Mitchell",
player == "-on Mitchell" ~ "Don Mitchell",
player == "-ud-ey -ueller" ~ "Dudley Mueller",
player == "-ud-ey Mueller" ~ "Dudley Mueller",
player == "Dud-ey Mueller" ~ "Dudley Mueller",
player == "Dudley Muelle-" ~ "Dudley Mueller",
player == "Dud-ey Mueller" ~ "Dudley Mueller",
player == "Dudley Muelle-" ~ "Dudley Mueller",
player == "G-a Holb---k" ~ "Gia Holbrook",
player == "G-a Holb--ok" ~ "Gia Holbrook",
player == "G-a Holbr-ok" ~ "Gia Holbrook",
player == "H-t-ie-d S-z-ki" ~ "Hatfield Suzuki",
player == "J-xo- B-c--ey" ~ "Jaxon Buckley",
player == "J-xo- B-ck-ey" ~ "Jaxon Buckley",
player == "J-xon B-ck-ey" ~ "Jaxon Buckley",
player == "J-x-n B-ck--y" ~ "Jaxon Buckley",
player == "J-x-n B-ckl-y" ~ "Jaxon Buckley",
player == "J-x-n Buckl-y" ~ "Jaxon Buckley",
player == "J-x-n Buckley" ~ "Jaxon Buckley",
player == "Jax-n Buckley" ~ "Jaxon Buckley",
player == "Jaxon B-ck-ey" ~ "Jaxon Buckley",
player == "Jaxon Buck-ey" ~ "Jaxon Buckley",
player == "Knight Triu-phant" ~ "Knight Triumphant",
player == "Malik Dest-ny" ~ "Malik Destiny",
player == "Mi-a -emma" ~ "Mira Lemma",
player == "Mira -emma" ~ "Mira Lemma",
player == "P-u-a --rn-p" ~ "Paula Turnip",
player == "P-u-a --rnip" ~ "Paula Turnip",
player == "Pau-a -urnip" ~ "Paula Turnip",
player == "Pi-ching -ac--ne" ~ "Pitching Machine",
player == "R-g-- --ie-r---" ~ "Rigby Friedrich",
player == "T-oma- Drac-ena" ~ "Thomas Dracaena",
player == "Thoma- Drac-ena" ~ "Thomas Dracaena",
player == "Thomas Drac-ena" ~ "Thomas Dracaena",
player == "--n--- Carve-" ~ "Sandie Carver",
TRUE ~ player)) %>%
group_by(timestampPOSIX, position_fct, player_name) %>%
summarise(percent = eDensity/total,
abs_percent = abs(eDensity)/total) %>%
mutate(log_percent = case_when(abs_percent == 0 ~ 0,
abs_percent > 0 ~ log(abs_percent)+6),
player_position = paste0(" ", as.character(position_fct), ": ", player_name)) %>%
# Plot begins here
ggplot(aes(x = timestampPOSIX,
y = percent,
colour = position_fct)) +
theme_bw() +
viridis::scale_fill_viridis(option = "plasma", discrete = TRUE) +
viridis::scale_colour_viridis(option = "plasma", discrete = TRUE) +
scale_y_continuous(labels = scales::percent_format()) +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1),
legend.position = "none") +
# Layer 1 (for animation)
geom_point(aes(#group = player_position,
size = percent),
position = "stack",
stat = "identity",
alpha = .5) +
# Layer 2 (to be excluded from animation's shadow/trace)
geom_text(aes(label = player_position,
size = log_percent),
stat = "identity",
position = "stack",
check_overlap = FALSE,
hjust = 0) +
ggtitle("") -> q
Next, animate the plot, which will initially compile as a gif, but only with 100 frames (for now).
q + transition_states(timestampPOSIX,
transition_length = 10,
state_length = 1) +
ease_aes("cubic-in") +
view_follow(fixed_x = TRUE) +
labs(title = 'Idol Board at {closest_state}') +
shadow_trail(distance = 1,
exclude_layer = 2) +
shadow_mark(alpha = .05,
exclude_layer = 2) +
enter_fade() + exit_fade() -> q1
Finally, render it as a video and save it (although I still needed to convert to .m4v using VLC and then transcode to .mp4 using HandBrake).
animate(q1,
nframes = 6000,
fps = 30,
renderer = av_renderer(),
detail = 3,
width = 1920,
height = 1080)
#anim_save("20210725-big_vid")
And, if all goes well, and you have lots of time and sufficient computing power, you get something like this: