igroffman commited on
Commit
734f76d
·
verified ·
1 Parent(s): 18618e7

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +116 -27
app.R CHANGED
@@ -517,7 +517,6 @@ create_bp_pdf <- function(bp_data, batter_name, output_file) {
517
  invisible(output_file)
518
  }
519
 
520
-
521
  parse_game_day <- function(df, tz = "America/New_York") {
522
  stopifnot("Date" %in% names(df))
523
  if (inherits(df$Date, "Date")) {
@@ -529,7 +528,6 @@ parse_game_day <- function(df, tz = "America/New_York") {
529
  }
530
  as.Date(df$Date[1])
531
  }
532
-
533
  create_at_bats_plot <- function(batter_data, player_name, game_key, pitch_colors,
534
  max_lines_per_col = 16L) {
535
  df <- dplyr::filter(batter_data, Batter == player_name)
@@ -629,7 +627,6 @@ plot_data <- df %>%
629
  panel.spacing = grid::unit(8, "pt")
630
  )
631
  }
632
-
633
  create_report_spray_chart <- function(game_data, player_name) {
634
  spray_data <- game_data %>%
635
  dplyr::filter(Batter == player_name) %>%
@@ -683,7 +680,6 @@ create_report_spray_chart <- function(game_data, player_name) {
683
  plot.title = ggplot2::element_text(hjust = 0.5, size = 9, face = "bold")
684
  )
685
  }
686
-
687
  create_report_contact_chart <- function(game_data, player_name) {
688
  contact_data <- game_data %>%
689
  filter(Batter == player_name) %>%
@@ -746,14 +742,12 @@ create_report_contact_chart <- function(game_data, player_name) {
746
  legend.key.width = unit(0.3, "cm")
747
  )
748
  }
749
-
750
  calculate_leaderboards <- function(df, team_meta_df = team_meta) {
751
 
752
  format_name <- function(name) {
753
  if (is.na(name)) return(name)
754
  stringr::str_replace(name, "^\\s*(\\w+)\\s*,\\s*(\\w+)\\s*$", "\\2 \\1")
755
  }
756
-
757
  get_logo <- function(team_abbr) {
758
  if (is.null(team_meta_df) || is.null(team_abbr) || is.na(team_abbr)) return("")
759
  team_abbr <- trimws(as.character(team_abbr))
@@ -781,7 +775,6 @@ calculate_leaderboards <- function(df, team_meta_df = team_meta) {
781
  # Game Info
782
  stadium <- if ("Stadium" %in% names(df)) unique(na.omit(df$Stadium))[1] else "Unknown"
783
  level <- if ("Level" %in% names(df)) unique(na.omit(df$Level))[1] else ""
784
-
785
  league <- if ("League" %in% names(df)) unique(na.omit(df$League))[1] else ""
786
 
787
  game_date <- if ("Date" %in% names(df)) {
@@ -806,7 +799,6 @@ game_date <- if ("Date" %in% names(df)) {
806
 
807
  if (is.na(parsed)) "N/A" else format(parsed, "%m/%d/%Y")
808
  } else "N/A"
809
-
810
  # Calculate final score from RunsScored
811
  teams <- unique(c(df$BatterTeam, df$PitcherTeam))
812
  teams <- teams[!is.na(teams)]
@@ -882,7 +874,6 @@ game_date <- if ("Date" %in% names(df)) {
882
  whiffs = top_whiffs
883
  )
884
  }
885
-
886
  create_simple_header <- function(player_name, game_date, bio_data = NULL) {
887
  if (!is.null(bio_data) && nrow(bio_data) > 0) {
888
  player_bio <- bio_data %>% filter(Batter == player_name)
@@ -918,6 +909,21 @@ grid::textGrob(
918
  )
919
  }
920
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
921
  create_postgame_pdf <- function(game_df, player_name, output_file, bio_data = NULL) {
922
  if (length(dev.list()) > 0) { try(dev.off(), silent = TRUE) }
923
 
@@ -931,6 +937,9 @@ create_postgame_pdf <- function(game_df, player_name, output_file, bio_data = NU
931
  game_day <- parse_game_day(batter_df, tz = "America/New_York")
932
  game_key <- format(game_day, "%Y-%m-%d")
933
 
 
 
 
934
  game_stats <- batter_df %>%
935
  summarise(
936
  PA = sum(PAindicator, na.rm = TRUE),
@@ -962,6 +971,8 @@ create_postgame_pdf <- function(game_df, player_name, output_file, bio_data = NU
962
  spray_plot <- create_report_spray_chart(game_df, player_name)
963
  contact_plot <- create_report_contact_chart(game_df, player_name)
964
 
 
 
965
  pitch_log <- pitch_sequence %>%
966
  filter(PitchCall == "InPlay") %>%
967
  mutate(
@@ -969,11 +980,44 @@ create_postgame_pdf <- function(game_df, player_name, output_file, bio_data = NU
969
  event = dplyr::case_when(
970
  !is.na(PlayResult) & PlayResult != "Undefined" ~ PlayResult, TRUE ~ "Out"
971
  ),
972
- EV = round(ExitSpeed), LA = round(Angle),
 
 
973
  Dist = ifelse(!is.na(Distance), round(Distance), NA),
974
- Velo = round(RelSpeed, 1)
975
- ) %>%
976
- select(PitchNumber, Pitcher, Throws, Balls, Strikes, TaggedPitchType, Velo, event, EV, LA, Dist)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
977
 
978
  chart_y <- 0.36
979
  chart_h <- 0.22
@@ -981,26 +1025,70 @@ create_postgame_pdf <- function(game_df, player_name, output_file, bio_data = NU
981
  table_title_y <- 0.13
982
  table_y <- 0.11
983
 
984
- draw_pitch_table <- function(df, y_top, row_height = 0.0135, cex = 0.58) {
985
- headers <- c("#","Pitcher","T","B","S","Pitch","Velo", "Event","EV","LA","Dist")
986
- widths <- c(0.03,0.15,0.03,0.04,0.04,0.09,0.05,0.06,0.20,0.05,0.05,0.06)
 
 
 
 
 
 
 
 
 
987
  x_start <- 0.5 - sum(widths)/2
988
  x_pos <- c(x_start, x_start + cumsum(widths[-length(widths)]))
 
 
989
  for (i in seq_along(headers)) {
990
  grid.rect(x = x_pos[i], y = y_top, width = widths[i]*0.985, height = row_height,
991
  just = c("left","top"), gp = gpar(fill = "#006F71", col = "black", lwd = 0.4))
992
  grid.text(headers[i], x = x_pos[i] + widths[i]*0.49, y = y_top - row_height*0.5,
993
  gp = gpar(col = "white", cex = cex, fontface = "bold"))
994
  }
 
 
995
  for (r in seq_len(nrow(df))) {
996
  y_row <- y_top - r*row_height
997
- row_vals <- c(
998
- df$PitchNumber[r], df$Pitcher[r], df$Throws[r], df$Balls[r], df$Strikes[r],
999
- df$TaggedPitchType[r], df$Velo[r], df$event[r],
1000
- ifelse(is.na(df$EV[r]), "-", df$EV[r]),
1001
- ifelse(is.na(df$LA[r]), "-", df$LA[r]),
1002
- ifelse(is.na(df$Dist[r]), "-", df$Dist[r])
1003
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1004
  for (i in seq_along(row_vals)) {
1005
  grid.rect(x = x_pos[i], y = y_row, width = widths[i]*0.985, height = row_height, just = c("left","top"),
1006
  gp = gpar(fill = ifelse(r %% 2 == 0, "#f7f7f7", "white"), col = "grey80", lwd = 0.3))
@@ -1048,23 +1136,24 @@ create_postgame_pdf <- function(game_df, player_name, output_file, bio_data = NU
1048
  print(contact_plot, newpage = FALSE)
1049
  popViewport()
1050
 
1051
- grid.text(paste(player_name, "-", game_key), x = 0.5, y = 0.13,
1052
  gp = gpar(fontface = "bold", cex = 0.98))
1053
 
1054
  rows_total <- nrow(pitch_log)
1055
  max_rows_first <- floor((0.11 - 0.02) / 0.0130)
1056
  rows_first <- min(rows_total, max_rows_first)
1057
  if (rows_first > 0) {
1058
- draw_pitch_table(pitch_log[1:rows_first, , drop = FALSE], y_top = 0.11, row_height = 0.0130, cex = 0.58)
 
1059
  }
1060
  next_row <- rows_first + 1
1061
  if (next_row <= rows_total) {
1062
  grid::grid.newpage()
1063
- draw_pitch_table(pitch_log[next_row:rows_total, , drop = FALSE], y_top = 0.97, row_height = 0.0175, cex = 0.62)
 
1064
  }
1065
  }
1066
 
1067
-
1068
  # ============================================================
1069
  # PITCHER POST-GAME PDF (Tableau-style)
1070
  # Fixes:
 
517
  invisible(output_file)
518
  }
519
 
 
520
  parse_game_day <- function(df, tz = "America/New_York") {
521
  stopifnot("Date" %in% names(df))
522
  if (inherits(df$Date, "Date")) {
 
528
  }
529
  as.Date(df$Date[1])
530
  }
 
531
  create_at_bats_plot <- function(batter_data, player_name, game_key, pitch_colors,
532
  max_lines_per_col = 16L) {
533
  df <- dplyr::filter(batter_data, Batter == player_name)
 
627
  panel.spacing = grid::unit(8, "pt")
628
  )
629
  }
 
630
  create_report_spray_chart <- function(game_data, player_name) {
631
  spray_data <- game_data %>%
632
  dplyr::filter(Batter == player_name) %>%
 
680
  plot.title = ggplot2::element_text(hjust = 0.5, size = 9, face = "bold")
681
  )
682
  }
 
683
  create_report_contact_chart <- function(game_data, player_name) {
684
  contact_data <- game_data %>%
685
  filter(Batter == player_name) %>%
 
742
  legend.key.width = unit(0.3, "cm")
743
  )
744
  }
 
745
  calculate_leaderboards <- function(df, team_meta_df = team_meta) {
746
 
747
  format_name <- function(name) {
748
  if (is.na(name)) return(name)
749
  stringr::str_replace(name, "^\\s*(\\w+)\\s*,\\s*(\\w+)\\s*$", "\\2 \\1")
750
  }
 
751
  get_logo <- function(team_abbr) {
752
  if (is.null(team_meta_df) || is.null(team_abbr) || is.na(team_abbr)) return("")
753
  team_abbr <- trimws(as.character(team_abbr))
 
775
  # Game Info
776
  stadium <- if ("Stadium" %in% names(df)) unique(na.omit(df$Stadium))[1] else "Unknown"
777
  level <- if ("Level" %in% names(df)) unique(na.omit(df$Level))[1] else ""
 
778
  league <- if ("League" %in% names(df)) unique(na.omit(df$League))[1] else ""
779
 
780
  game_date <- if ("Date" %in% names(df)) {
 
799
 
800
  if (is.na(parsed)) "N/A" else format(parsed, "%m/%d/%Y")
801
  } else "N/A"
 
802
  # Calculate final score from RunsScored
803
  teams <- unique(c(df$BatterTeam, df$PitcherTeam))
804
  teams <- teams[!is.na(teams)]
 
874
  whiffs = top_whiffs
875
  )
876
  }
 
877
  create_simple_header <- function(player_name, game_date, bio_data = NULL) {
878
  if (!is.null(bio_data) && nrow(bio_data) > 0) {
879
  player_bio <- bio_data %>% filter(Batter == player_name)
 
909
  )
910
  }
911
 
912
+ # Helper function to check if bat tracking data is available
913
+ has_bat_tracking <- function(df) {
914
+ bat_cols <- c("BatSpeed", "VerticalAttackAngle", "HorizontalAttackAngle")
915
+ cols_present <- bat_cols %in% names(df)
916
+ if (!all(cols_present)) return(FALSE)
917
+
918
+ # Check if there's at least some non-NA data in any of these columns
919
+ any_data <- any(
920
+ !is.na(df$BatSpeed) |
921
+ !is.na(df$VerticalAttackAngle) |
922
+ !is.na(df$HorizontalAttackAngle)
923
+ )
924
+ return(any_data)
925
+ }
926
+
927
  create_postgame_pdf <- function(game_df, player_name, output_file, bio_data = NULL) {
928
  if (length(dev.list()) > 0) { try(dev.off(), silent = TRUE) }
929
 
 
937
  game_day <- parse_game_day(batter_df, tz = "America/New_York")
938
  game_key <- format(game_day, "%Y-%m-%d")
939
 
940
+ # Check if bat tracking data is available
941
+ bat_tracking_available <- has_bat_tracking(batter_df)
942
+
943
  game_stats <- batter_df %>%
944
  summarise(
945
  PA = sum(PAindicator, na.rm = TRUE),
 
971
  spray_plot <- create_report_spray_chart(game_df, player_name)
972
  contact_plot <- create_report_contact_chart(game_df, player_name)
973
 
974
+ # Build pitch log with conditional bat tracking columns
975
+ # New order: Inning, Pitcher, Count, Pitch, Velo, IVB, HB, VAA, EV, LA, Dist, Bat Speed, AA, HAA
976
  pitch_log <- pitch_sequence %>%
977
  filter(PitchCall == "InPlay") %>%
978
  mutate(
 
980
  event = dplyr::case_when(
981
  !is.na(PlayResult) & PlayResult != "Undefined" ~ PlayResult, TRUE ~ "Out"
982
  ),
983
+ Count = paste0(Balls, "-", Strikes),
984
+ EV = round(ExitSpeed),
985
+ LA = round(Angle),
986
  Dist = ifelse(!is.na(Distance), round(Distance), NA),
987
+ Velo = round(RelSpeed, 1),
988
+ # Pitch movement metrics
989
+ IVB = ifelse("InducedVertBreak" %in% names(.) & !is.na(InducedVertBreak),
990
+ round(InducedVertBreak, 1), NA),
991
+ HB = ifelse("HorzBreak" %in% names(.) & !is.na(HorzBreak),
992
+ round(HorzBreak, 1), NA),
993
+ # Vertical Approach Angle (pitch)
994
+ VAA = ifelse("VertApprAngle" %in% names(.) & !is.na(VertApprAngle),
995
+ round(VertApprAngle, 1), NA)
996
+ )
997
+
998
+ # Add bat tracking columns if available
999
+ if (bat_tracking_available) {
1000
+ pitch_log <- pitch_log %>%
1001
+ mutate(
1002
+ BatSpd = ifelse("BatSpeed" %in% names(.) & !is.na(BatSpeed),
1003
+ round(BatSpeed, 1), NA),
1004
+ AA = ifelse("VerticalAttackAngle" %in% names(.) & !is.na(VerticalAttackAngle),
1005
+ round(VerticalAttackAngle, 1), NA),
1006
+ HAA = ifelse("HorizontalAttackAngle" %in% names(.) & !is.na(HorizontalAttackAngle),
1007
+ round(HorizontalAttackAngle, 1), NA)
1008
+ )
1009
+ }
1010
+
1011
+ # Select columns based on availability
1012
+ if (bat_tracking_available) {
1013
+ pitch_log <- pitch_log %>%
1014
+ select(PitchNumber, Inning, Pitcher, Count, TaggedPitchType, Velo, IVB, HB, VAA,
1015
+ event, EV, LA, Dist, BatSpd, AA, HAA)
1016
+ } else {
1017
+ pitch_log <- pitch_log %>%
1018
+ select(PitchNumber, Inning, Pitcher, Count, TaggedPitchType, Velo, IVB, HB, VAA,
1019
+ event, EV, LA, Dist)
1020
+ }
1021
 
1022
  chart_y <- 0.36
1023
  chart_h <- 0.22
 
1025
  table_title_y <- 0.13
1026
  table_y <- 0.11
1027
 
1028
+ # Updated draw function with bat tracking support
1029
+ draw_pitch_table <- function(df, y_top, row_height = 0.0135, cex = 0.58, include_bat_tracking = FALSE) {
1030
+ if (include_bat_tracking) {
1031
+ # Headers with bat tracking: #, Inn, Pitcher, Count, Pitch, Velo, IVB, HB, VAA, Event, EV, LA, Dist, BatSpd, AA, HAA
1032
+ headers <- c("#", "Inn", "Pitcher", "Count", "Pitch", "Velo", "IVB", "HB", "VAA", "Event", "EV", "LA", "Dist", "BatSpd", "AA", "HAA")
1033
+ widths <- c(0.025, 0.03, 0.12, 0.04, 0.065, 0.04, 0.04, 0.04, 0.04, 0.065, 0.035, 0.035, 0.04, 0.045, 0.04, 0.04)
1034
+ } else {
1035
+ # Headers without bat tracking: #, Inn, Pitcher, Count, Pitch, Velo, IVB, HB, VAA, Event, EV, LA, Dist
1036
+ headers <- c("#", "Inn", "Pitcher", "Count", "Pitch", "Velo", "IVB", "HB", "VAA", "Event", "EV", "LA", "Dist")
1037
+ widths <- c(0.03, 0.035, 0.15, 0.05, 0.08, 0.05, 0.05, 0.05, 0.05, 0.08, 0.045, 0.045, 0.05)
1038
+ }
1039
+
1040
  x_start <- 0.5 - sum(widths)/2
1041
  x_pos <- c(x_start, x_start + cumsum(widths[-length(widths)]))
1042
+
1043
+ # Draw headers
1044
  for (i in seq_along(headers)) {
1045
  grid.rect(x = x_pos[i], y = y_top, width = widths[i]*0.985, height = row_height,
1046
  just = c("left","top"), gp = gpar(fill = "#006F71", col = "black", lwd = 0.4))
1047
  grid.text(headers[i], x = x_pos[i] + widths[i]*0.49, y = y_top - row_height*0.5,
1048
  gp = gpar(col = "white", cex = cex, fontface = "bold"))
1049
  }
1050
+
1051
+ # Draw rows
1052
  for (r in seq_len(nrow(df))) {
1053
  y_row <- y_top - r*row_height
1054
+
1055
+ if (include_bat_tracking) {
1056
+ row_vals <- c(
1057
+ df$PitchNumber[r],
1058
+ ifelse(is.na(df$Inning[r]), "-", df$Inning[r]),
1059
+ df$Pitcher[r],
1060
+ df$Count[r],
1061
+ df$TaggedPitchType[r],
1062
+ ifelse(is.na(df$Velo[r]), "-", df$Velo[r]),
1063
+ ifelse(is.na(df$IVB[r]), "-", df$IVB[r]),
1064
+ ifelse(is.na(df$HB[r]), "-", df$HB[r]),
1065
+ ifelse(is.na(df$VAA[r]), "-", df$VAA[r]),
1066
+ df$event[r],
1067
+ ifelse(is.na(df$EV[r]), "-", df$EV[r]),
1068
+ ifelse(is.na(df$LA[r]), "-", df$LA[r]),
1069
+ ifelse(is.na(df$Dist[r]), "-", df$Dist[r]),
1070
+ ifelse(is.na(df$BatSpd[r]), "-", df$BatSpd[r]),
1071
+ ifelse(is.na(df$AA[r]), "-", df$AA[r]),
1072
+ ifelse(is.na(df$HAA[r]), "-", df$HAA[r])
1073
+ )
1074
+ } else {
1075
+ row_vals <- c(
1076
+ df$PitchNumber[r],
1077
+ ifelse(is.na(df$Inning[r]), "-", df$Inning[r]),
1078
+ df$Pitcher[r],
1079
+ df$Count[r],
1080
+ df$TaggedPitchType[r],
1081
+ ifelse(is.na(df$Velo[r]), "-", df$Velo[r]),
1082
+ ifelse(is.na(df$IVB[r]), "-", df$IVB[r]),
1083
+ ifelse(is.na(df$HB[r]), "-", df$HB[r]),
1084
+ ifelse(is.na(df$VAA[r]), "-", df$VAA[r]),
1085
+ df$event[r],
1086
+ ifelse(is.na(df$EV[r]), "-", df$EV[r]),
1087
+ ifelse(is.na(df$LA[r]), "-", df$LA[r]),
1088
+ ifelse(is.na(df$Dist[r]), "-", df$Dist[r])
1089
+ )
1090
+ }
1091
+
1092
  for (i in seq_along(row_vals)) {
1093
  grid.rect(x = x_pos[i], y = y_row, width = widths[i]*0.985, height = row_height, just = c("left","top"),
1094
  gp = gpar(fill = ifelse(r %% 2 == 0, "#f7f7f7", "white"), col = "grey80", lwd = 0.3))
 
1136
  print(contact_plot, newpage = FALSE)
1137
  popViewport()
1138
 
1139
+ grid.text(paste(player_name, "-", game_key, "- Batted Ball Log"), x = 0.5, y = 0.13,
1140
  gp = gpar(fontface = "bold", cex = 0.98))
1141
 
1142
  rows_total <- nrow(pitch_log)
1143
  max_rows_first <- floor((0.11 - 0.02) / 0.0130)
1144
  rows_first <- min(rows_total, max_rows_first)
1145
  if (rows_first > 0) {
1146
+ draw_pitch_table(pitch_log[1:rows_first, , drop = FALSE], y_top = 0.11, row_height = 0.0130,
1147
+ cex = 0.52, include_bat_tracking = bat_tracking_available)
1148
  }
1149
  next_row <- rows_first + 1
1150
  if (next_row <= rows_total) {
1151
  grid::grid.newpage()
1152
+ draw_pitch_table(pitch_log[next_row:rows_total, , drop = FALSE], y_top = 0.97, row_height = 0.0175,
1153
+ cex = 0.56, include_bat_tracking = bat_tracking_available)
1154
  }
1155
  }
1156
 
 
1157
  # ============================================================
1158
  # PITCHER POST-GAME PDF (Tableau-style)
1159
  # Fixes: