igroffman commited on
Commit
e78d32c
·
verified ·
1 Parent(s): bd407b4

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +61 -426
app.R CHANGED
@@ -1,4 +1,3 @@
1
- # app.R
2
  library(shiny)
3
  library(dplyr)
4
  library(ggplot2)
@@ -1025,378 +1024,6 @@ create_pitcher_game_line <- function(game_data) {
1025
  )
1026
  }
1027
 
1028
- # =====================================================================
1029
- # ================== ADVANCED PITCHER REPORT CODE ===================
1030
- # =====================================================================
1031
-
1032
- # Color palettes for heatmap tables
1033
- make_color_fn <- function(min_val, max_val) {
1034
- function(x) {
1035
- normalized <- (x - min_val) / (max_val - min_val)
1036
- normalized <- pmax(0, pmin(1, normalized))
1037
- rgb(colorRamp(c("#648FFF", "#FFFFFF", "#FFB000"))(normalized), maxColorValue = 255)
1038
- }
1039
- }
1040
-
1041
- make_color_fn_reverse <- function(min_val, max_val) {
1042
- function(x) {
1043
- normalized <- (x - min_val) / (max_val - min_val)
1044
- normalized <- pmax(0, pmin(1, normalized))
1045
- rgb(colorRamp(c("#FFB000", "#FFFFFF", "#648FFF"))(normalized), maxColorValue = 255)
1046
- }
1047
- }
1048
-
1049
- # Enhanced pitcher summary table with color coding
1050
- create_advanced_pitcher_summary <- function(data, player_name) {
1051
- pitcher_data <- data %>% filter(Pitcher == player_name)
1052
-
1053
- # Calculate comprehensive statistics
1054
- summary_stats <- pitcher_data %>%
1055
- summarise(
1056
- BF = n_distinct(paste(Inning, Batter, PAofInning)),
1057
- P = n(),
1058
- K = sum(KorBB == "Strikeout", na.rm = TRUE),
1059
- BB = sum(WalkIndicator, na.rm = TRUE),
1060
- H = sum(PlayResult %in% c('Single','Double','Triple','HomeRun'), na.rm = TRUE),
1061
- XBH = sum(PlayResult %in% c('Double','Triple','HomeRun'), na.rm = TRUE),
1062
- HR = sum(PlayResult == 'HomeRun', na.rm = TRUE),
1063
- HBP = sum(HBPIndicator, na.rm = TRUE),
1064
- `Strike%` = 100 * sum(!PitchCall %in% c("BallCalled", "HitByPitch", "BallinDirt", "BallIntentional")) / n(),
1065
- `CSW%` = 100 * sum(PitchCall %in% c("StrikeCalled", "StrikeSwinging")) / n(),
1066
- `Whiff%` = 100 * sum(WhiffIndicator, na.rm = TRUE) / sum(SwingIndicator, na.rm = TRUE),
1067
- `Chase%` = 100 * sum(Chaseindicator, na.rm = TRUE) / sum(SwingIndicator == 1 & StrikeZoneIndicator == 0, na.rm = TRUE),
1068
- `Zone%` = 100 * sum(StrikeZoneIndicator, na.rm = TRUE) / n(),
1069
- `In-Zone Whiff%` = 100 * sum(Zwhiffind, na.rm = TRUE) / sum(Zswing, na.rm = TRUE),
1070
- .groups = "drop"
1071
- )
1072
-
1073
- return(summary_stats)
1074
- }
1075
-
1076
- # Pitch characteristics table with movement and location metrics
1077
- create_advanced_pitch_characteristics <- function(data, player_name) {
1078
- pitcher_data <- data %>%
1079
- filter(Pitcher == player_name, !is.na(TaggedPitchType), TaggedPitchType != "Other")
1080
-
1081
- pitch_stats <- pitcher_data %>%
1082
- group_by(Pitch = TaggedPitchType) %>%
1083
- summarise(
1084
- Count = n(),
1085
- `Usage%` = 100 * n() / nrow(pitcher_data),
1086
- `Avg Velo` = round(mean(RelSpeed, na.rm = TRUE), 1),
1087
- `Max Velo` = round(max(RelSpeed, na.rm = TRUE), 1),
1088
- `Avg Spin` = round(mean(SpinRate, na.rm = TRUE), 0),
1089
- `Max Spin` = round(max(SpinRate, na.rm = TRUE), 0),
1090
- `Avg IVB` = round(mean(InducedVertBreak, na.rm = TRUE), 1),
1091
- `Avg HB` = round(mean(HorzBreak, na.rm = TRUE), 1),
1092
- `hRel` = round(mean(RelSide, na.rm = TRUE), 1),
1093
- `vRel` = round(mean(RelHeight, na.rm = TRUE), 1),
1094
- `Ext` = round(mean(Extension, na.rm = TRUE), 1),
1095
- `Strike%` = 100 * sum(!PitchCall %in% c("BallCalled", "BallinDirt", "BallIntentional")) / n(),
1096
- `Whiff%` = 100 * sum(WhiffIndicator, na.rm = TRUE) / sum(SwingIndicator, na.rm = TRUE),
1097
- `Zone%` = 100 * sum(StrikeZoneIndicator, na.rm = TRUE) / n(),
1098
- `Chase%` = 100 * sum(Chaseindicator, na.rm = TRUE) / sum(SwingIndicator == 1 & StrikeZoneIndicator == 0, na.rm = TRUE),
1099
- .groups = "drop"
1100
- ) %>%
1101
- arrange(desc(`Usage%`))
1102
-
1103
- return(pitch_stats)
1104
- }
1105
-
1106
- # Enhanced break plot with pitcher handedness
1107
- create_advanced_break_plot <- function(data, player_name, pitch_colors) {
1108
- pitcher_data <- data %>%
1109
- filter(Pitcher == player_name, !is.na(TaggedPitchType), TaggedPitchType != "Other",
1110
- !is.na(HorzBreak), !is.na(InducedVertBreak))
1111
-
1112
- if (nrow(pitcher_data) == 0) {
1113
- return(ggplot() + theme_void() + ggtitle("Pitch Movement") +
1114
- theme(plot.title = element_text(size = 14, face = "bold", hjust = 0.5)))
1115
- }
1116
-
1117
- # Determine pitcher handedness
1118
- pitcher_hand <- pitcher_data$PitcherThrows[1]
1119
- x_mult <- if (pitcher_hand == "Right") -1 else 1
1120
-
1121
- # Calculate pitch averages for larger markers
1122
- pitch_avgs <- pitcher_data %>%
1123
- group_by(TaggedPitchType) %>%
1124
- summarise(
1125
- HB_avg = mean(HorzBreak * x_mult, na.rm = TRUE),
1126
- IVB_avg = mean(InducedVertBreak, na.rm = TRUE),
1127
- Velo_avg = round(mean(RelSpeed, na.rm = TRUE)),
1128
- .groups = "drop"
1129
- )
1130
-
1131
- p <- ggplot() +
1132
- geom_hline(yintercept = 0, color = "#808080", alpha = 0.5, linetype = "dashed") +
1133
- geom_vline(xintercept = 0, color = "#808080", alpha = 0.5, linetype = "dashed") +
1134
- geom_point(data = pitcher_data,
1135
- aes(x = HorzBreak * x_mult, y = InducedVertBreak, fill = TaggedPitchType),
1136
- shape = 21, size = 4, color = "black", stroke = 0.4, alpha = 0.85) +
1137
- geom_point(data = pitch_avgs,
1138
- aes(x = HB_avg, y = IVB_avg, fill = TaggedPitchType),
1139
- shape = 21, size = 8, color = "black", stroke = 0.5, alpha = 1) +
1140
- geom_text(data = pitch_avgs,
1141
- aes(x = HB_avg, y = IVB_avg, label = Velo_avg),
1142
- size = 4, fontface = "bold") +
1143
- scale_fill_manual(values = pitch_colors, name = "Pitch Type") +
1144
- coord_fixed(xlim = c(-25, 25), ylim = c(-25, 25)) +
1145
- labs(title = "Pitch Movement",
1146
- x = "Horizontal Break (in)",
1147
- y = "Induced Vertical Break (in)") +
1148
- theme_minimal(base_size = 12) +
1149
- theme(
1150
- plot.title = element_text(size = 16, face = "bold", hjust = 0.5),
1151
- legend.position = "none",
1152
- panel.grid.minor = element_blank(),
1153
- aspect.ratio = 1
1154
- )
1155
-
1156
- # Add side labels based on handedness
1157
- if (pitcher_hand == "Right") {
1158
- p <- p +
1159
- annotate("text", x = -24, y = -24, label = "← Glove Side",
1160
- hjust = 0, vjust = 0, size = 3, fontface = "italic",
1161
- color = "black") +
1162
- annotate("text", x = 24, y = -24, label = "Arm Side →",
1163
- hjust = 1, vjust = 0, size = 3, fontface = "italic",
1164
- color = "black")
1165
- } else {
1166
- p <- p +
1167
- annotate("text", x = 24, y = -24, label = "← Arm Side",
1168
- hjust = 1, vjust = 0, size = 3, fontface = "italic",
1169
- color = "black") +
1170
- annotate("text", x = -24, y = -24, label = "Glove Side →",
1171
- hjust = 0, vjust = 0, size = 3, fontface = "italic",
1172
- color = "black")
1173
- }
1174
-
1175
- return(p)
1176
- }
1177
-
1178
- # Velocity distribution KDE plots by pitch type
1179
- create_velocity_distribution_plot <- function(data, player_name, pitch_colors) {
1180
- pitcher_data <- data %>%
1181
- filter(Pitcher == player_name, !is.na(TaggedPitchType), TaggedPitchType != "Other",
1182
- !is.na(RelSpeed))
1183
-
1184
- if (nrow(pitcher_data) == 0) {
1185
- return(ggplot() + theme_void() + ggtitle("Velocity Distribution") +
1186
- theme(plot.title = element_text(size = 14, face = "bold", hjust = 0.5)))
1187
- }
1188
-
1189
- # Order pitch types by frequency
1190
- pitch_order <- pitcher_data %>%
1191
- count(TaggedPitchType, sort = TRUE) %>%
1192
- pull(TaggedPitchType)
1193
-
1194
- pitcher_data <- pitcher_data %>%
1195
- mutate(TaggedPitchType = factor(TaggedPitchType, levels = pitch_order))
1196
-
1197
- # Calculate means for each pitch type
1198
- pitch_means <- pitcher_data %>%
1199
- group_by(TaggedPitchType) %>%
1200
- summarise(mean_velo = mean(RelSpeed, na.rm = TRUE), .groups = "drop")
1201
-
1202
- ggplot(pitcher_data, aes(x = RelSpeed, fill = TaggedPitchType)) +
1203
- geom_density(alpha = 0.7, color = "black", size = 0.3) +
1204
- geom_vline(data = pitch_means, aes(xintercept = mean_velo, color = TaggedPitchType),
1205
- linetype = "dashed", size = 0.8) +
1206
- facet_wrap(~ TaggedPitchType, ncol = 1, strip.position = "left") +
1207
- scale_fill_manual(values = pitch_colors) +
1208
- scale_color_manual(values = pitch_colors) +
1209
- labs(title = "Velocity Distribution by Pitch Type",
1210
- x = "Velocity (mph)",
1211
- y = "") +
1212
- theme_minimal(base_size = 11) +
1213
- theme(
1214
- plot.title = element_text(size = 14, face = "bold", hjust = 0.5),
1215
- legend.position = "none",
1216
- strip.text.y.left = element_text(angle = 0, hjust = 1, face = "bold", size = 10),
1217
- strip.placement = "outside",
1218
- panel.grid.major.y = element_blank(),
1219
- panel.grid.minor = element_blank(),
1220
- axis.text.y = element_blank(),
1221
- axis.ticks.y = element_blank()
1222
- )
1223
- }
1224
-
1225
- # Pitch locations by batter side
1226
- create_location_by_side_plot <- function(data, player_name, batter_side, pitch_colors) {
1227
- pitcher_data <- data %>%
1228
- filter(Pitcher == player_name, BatterSide == batter_side,
1229
- !is.na(TaggedPitchType), TaggedPitchType != "Other",
1230
- !is.na(PlateLocSide), !is.na(PlateLocHeight))
1231
-
1232
- if (nrow(pitcher_data) == 0) {
1233
- return(ggplot() +
1234
- annotate("rect", xmin = -0.83, xmax = 0.83, ymin = 1.5, ymax = 3.38,
1235
- fill = NA, color = "black", size = 0.5) +
1236
- theme_void() +
1237
- ggtitle(paste0("Locations vs ", batter_side, "HB")) +
1238
- theme(plot.title = element_text(size = 12, face = "bold", hjust = 0.5)))
1239
- }
1240
-
1241
- # Count pitches by type
1242
- pitch_count <- n_distinct(pitcher_data$TaggedPitchType)
1243
-
1244
- ggplot(pitcher_data, aes(x = -PlateLocSide, y = PlateLocHeight, fill = TaggedPitchType)) +
1245
- geom_point(alpha = 0.8, shape = 21, color = "black", stroke = 0.5, size = 4) +
1246
- facet_wrap(~ TaggedPitchType, ncol = pitch_count,
1247
- labeller = labeller(TaggedPitchType = ~ "")) +
1248
- annotate("rect", xmin = -0.83, xmax = 0.83, ymin = 1.5, ymax = 3.38,
1249
- fill = NA, color = "black", size = 0.5) +
1250
- geom_segment(aes(x = -0.708, y = 0.15, xend = 0.708, yend = 0.15), color = "black", size = 0.5) +
1251
- geom_segment(aes(x = -0.708, y = 0.30, xend = -0.708, yend = 0.15), color = "black", size = 0.5) +
1252
- geom_segment(aes(x = 0.708, y = 0.30, xend = 0.708, yend = 0.15), color = "black", size = 0.5) +
1253
- geom_segment(aes(x = -0.708, y = 0.30, xend = 0.000, yend = 0.50), color = "black", size = 0.5) +
1254
- geom_segment(aes(x = 0.708, y = 0.30, xend = 0.000, yend = 0.50), color = "black", size = 0.5) +
1255
- scale_fill_manual(values = pitch_colors) +
1256
- scale_x_continuous(limits = c(-2, 2)) +
1257
- scale_y_continuous(limits = c(0, 4.5)) +
1258
- coord_fixed() +
1259
- ggtitle(paste0("Pitch Locations vs ", batter_side, "HB")) +
1260
- theme_void() +
1261
- theme(
1262
- plot.title = element_text(size = 12, face = "bold", hjust = 0.5),
1263
- legend.position = "none",
1264
- strip.text = element_text(size = 10, face = "bold"),
1265
- strip.placement = "outside"
1266
- )
1267
- }
1268
-
1269
- # Create the comprehensive advanced pitcher PDF
1270
- create_advanced_pitcher_pdf <- function(game_df, pitcher_name, output_file) {
1271
- if (length(dev.list()) > 0) try(dev.off(), silent = TRUE)
1272
-
1273
- pitch_colors <- c(
1274
- "Fastball" = "#FA8072", "Four-Seam" = "#FA8072", "Sinker" = "#fdae61",
1275
- "Slider" = "#A020F0", "Sweeper" = "magenta", "Curveball" = "#2c7bb6",
1276
- "ChangeUp" = "#90EE90", "Splitter" = "#90EE32", "Cutter" = "red"
1277
- )
1278
-
1279
- # Filter to pitcher's data
1280
- pitcher_df <- game_df %>% filter(Pitcher == pitcher_name)
1281
- if (nrow(pitcher_df) == 0) {
1282
- pdf(output_file, width = 11, height = 17)
1283
- grid.newpage()
1284
- grid.text("No data available for this pitcher",
1285
- gp = gpar(fontsize = 16, fontface = "bold"))
1286
- dev.off()
1287
- return(output_file)
1288
- }
1289
-
1290
- game_day <- parse_game_day(pitcher_df)
1291
-
1292
- # Generate all components
1293
- summary_stats <- create_advanced_pitcher_summary(pitcher_df, pitcher_name)
1294
- pitch_char <- create_advanced_pitch_characteristics(pitcher_df, pitcher_name)
1295
-
1296
- break_plot <- create_advanced_break_plot(pitcher_df, pitcher_name, pitch_colors)
1297
- velo_plot <- create_velocity_distribution_plot(pitcher_df, pitcher_name, pitch_colors)
1298
- location_lhb <- create_location_by_side_plot(pitcher_df, pitcher_name, "Left", pitch_colors)
1299
- location_rhb <- create_location_by_side_plot(pitcher_df, pitcher_name, "Right", pitch_colors)
1300
-
1301
- # Create PDF
1302
- pdf(output_file, width = 11, height = 17)
1303
- on.exit(try(dev.off(), silent = TRUE), add = TRUE)
1304
-
1305
- grid.newpage()
1306
-
1307
- # Header
1308
- pushViewport(viewport(x = 0.5, y = 0.97, width = 1, height = 0.06, just = c("center", "top")))
1309
- grid.text(paste(pitcher_name, "- Advanced Pitcher Report -", format(game_day, "%m/%d/%y")),
1310
- gp = gpar(fontface = "bold", cex = 2.0, col = "#006F71"))
1311
- popViewport()
1312
-
1313
- # Game Summary Table
1314
- grid.text("Game Summary", x = 0.5, y = 0.90,
1315
- gp = gpar(fontface = "bold", cex = 1.2, col = "#006F71"))
1316
-
1317
- summary_headers <- names(summary_stats)
1318
- summary_values <- as.numeric(summary_stats[1,])
1319
- summary_widths <- rep(0.06, length(summary_headers))
1320
-
1321
- x_start <- 0.5 - sum(summary_widths)/2
1322
- x_pos <- c(x_start, x_start + cumsum(summary_widths[-length(summary_widths)]))
1323
- y_top <- 0.88
1324
- row_h <- 0.022
1325
-
1326
- # Draw summary table
1327
- for (i in seq_along(summary_headers)) {
1328
- grid.rect(x = x_pos[i], y = y_top, width = summary_widths[i] * 0.985, height = row_h,
1329
- just = c("left", "top"), gp = gpar(fill = "#006F71", col = "black", lwd = 0.5))
1330
- grid.text(summary_headers[i], x = x_pos[i] + summary_widths[i] * 0.49, y = y_top - row_h * 0.5,
1331
- gp = gpar(col = "white", cex = 0.65, fontface = "bold"))
1332
-
1333
- grid.rect(x = x_pos[i], y = y_top - row_h, width = summary_widths[i] * 0.985, height = row_h,
1334
- just = c("left", "top"), gp = gpar(fill = "white", col = "black", lwd = 0.4))
1335
- grid.text(sprintf("%.1f", summary_values[i]),
1336
- x = x_pos[i] + summary_widths[i] * 0.49, y = y_top - row_h * 1.5,
1337
- gp = gpar(cex = 0.65))
1338
- }
1339
-
1340
- # Pitch Characteristics Table
1341
- grid.text("Pitch Characteristics", x = 0.5, y = 0.82,
1342
- gp = gpar(fontface = "bold", cex = 1.2, col = "#006F71"))
1343
-
1344
- char_headers <- names(pitch_char)
1345
- char_widths <- c(0.10, rep(0.055, length(char_headers) - 1))
1346
-
1347
- x_start_char <- 0.5 - sum(char_widths)/2
1348
- x_pos_char <- c(x_start_char, x_start_char + cumsum(char_widths[-length(char_widths)]))
1349
- y_top_char <- 0.80
1350
- row_h_char <- 0.018
1351
-
1352
- # Draw pitch characteristics table header
1353
- for (i in seq_along(char_headers)) {
1354
- grid.rect(x = x_pos_char[i], y = y_top_char, width = char_widths[i] * 0.985, height = row_h_char,
1355
- just = c("left", "top"), gp = gpar(fill = "#006F71", col = "black", lwd = 0.5))
1356
- grid.text(char_headers[i], x = x_pos_char[i] + char_widths[i] * 0.49, y = y_top_char - row_h_char * 0.5,
1357
- gp = gpar(col = "white", cex = 0.60, fontface = "bold"))
1358
- }
1359
-
1360
- # Draw pitch characteristics table rows
1361
- for (r in 1:min(nrow(pitch_char), 6)) {
1362
- y_row <- y_top_char - r * row_h_char
1363
- for (i in seq_along(char_headers)) {
1364
- val <- pitch_char[[i]][r]
1365
- bg <- if (r %% 2 == 0) "#f7f7f7" else "white"
1366
- grid.rect(x = x_pos_char[i], y = y_row, width = char_widths[i] * 0.985, height = row_h_char,
1367
- just = c("left", "top"), gp = gpar(fill = bg, col = "grey80", lwd = 0.3))
1368
- display_val <- if (is.numeric(val)) sprintf("%.1f", val) else as.character(val)
1369
- grid.text(display_val, x = x_pos_char[i] + char_widths[i] * 0.49, y = y_row - row_h_char * 0.5,
1370
- gp = gpar(cex = 0.58))
1371
- }
1372
- }
1373
-
1374
- # Plot Movement (large, left)
1375
- pushViewport(viewport(x = 0.25, y = 0.57, width = 0.45, height = 0.24, just = c("center", "top")))
1376
- print(break_plot, newpage = FALSE)
1377
- popViewport()
1378
-
1379
- # Plot Velocity Distribution (large, right)
1380
- pushViewport(viewport(x = 0.75, y = 0.57, width = 0.45, height = 0.24, just = c("center", "top")))
1381
- print(velo_plot, newpage = FALSE)
1382
- popViewport()
1383
-
1384
- # Location plots
1385
- pushViewport(viewport(x = 0.25, y = 0.28, width = 0.45, height = 0.18, just = c("center", "top")))
1386
- print(location_lhb, newpage = FALSE)
1387
- popViewport()
1388
-
1389
- pushViewport(viewport(x = 0.75, y = 0.28, width = 0.45, height = 0.18, just = c("center", "top")))
1390
- print(location_rhb, newpage = FALSE)
1391
- popViewport()
1392
-
1393
- # Footer
1394
- grid.text("Data: TrackMan | Report Generated: Coastal Carolina Baseball",
1395
- x = 0.5, y = 0.03, gp = gpar(cex = 0.8, col = "grey50"))
1396
-
1397
- invisible(output_file)
1398
- }
1399
-
1400
  create_pitcher_pitch_char <- function(game_data) {
1401
  game_data %>%
1402
  filter(TaggedPitchType != "Other", !is.na(TaggedPitchType)) %>%
@@ -2292,61 +1919,69 @@ server <- function(input, output, session) {
2292
  }
2293
  })
2294
 
2295
-
2296
- output$download_pitcher <- downloadHandler(
2297
- filename = function() {
2298
- df <- data_pitcher(); req(df, input$pitcher_name)
2299
- pitcher_clean <- gsub(" ", "_", input$pitcher_name)
2300
- date_str <- format(parse_game_day(df %>% filter(Pitcher == input$pitcher_name)), "%Y%m%d")
2301
- paste0(pitcher_clean, "_", date_str, "_Advanced_Pitcher_Report.pdf")
2302
- },
2303
- content = function(file) {
2304
- df <- data_pitcher(); req(df, input$pitcher_name)
2305
- withProgress(message='Generating Advanced Pitcher PDF', value=0, {
2306
- incProgress(.3, detail="Processing data...")
2307
- incProgress(.4, detail="Creating visualizations...")
2308
- create_advanced_pitcher_pdf(df, input$pitcher_name, file)
2309
- incProgress(.3, detail="Finalizing report...")
2310
- })
2311
- showNotification("✅ Advanced pitcher report generated!", type="message", duration=3)
2312
- },
2313
- contentType = "application/pdf"
2314
- )
2315
-
2316
- # Update bulk download to use new function
2317
- output$download_all_coastal_pitchers <- downloadHandler(
2318
- filename = function() {
2319
- df <- data_pitcher(); req(df)
2320
- paste0("Coastal_Advanced_Pitcher_Reports_", format(parse_game_day(df), "%Y%m%d"), ".zip")
2321
- },
2322
- content = function(file) {
2323
- df <- data_pitcher(); req(df)
2324
- pitchers <- df %>% dplyr::filter(PitcherTeam == "COA_CHA") %>%
2325
- dplyr::pull(Pitcher) %>% unique() %>% na.omit() %>% sort()
2326
- if (!length(pitchers)) {
2327
- showNotification("No Coastal pitchers found", type="error", duration=5)
2328
- return(NULL)
2329
- }
2330
- withProgress(message='Generating Coastal Advanced Pitcher Reports', value=0, {
2331
- tmp <- tempdir(); pdfs <- character(0); total <- length(pitchers)
2332
- for (i in seq_along(pitchers)) {
2333
- ply <- pitchers[i]; incProgress(1/total, detail=paste("Report for", ply))
2334
- out <- file.path(tmp, paste0(gsub(" ","_",ply), "_",
2335
- format(parse_game_day(df), "%Y%m%d"),
2336
- "_Advanced_Pitcher_Report.pdf"))
2337
- try(create_advanced_pitcher_pdf(df, ply, out), silent = TRUE)
2338
- if (file.exists(out)) pdfs <- c(pdfs, out)
2339
- }
2340
- if (!length(pdfs)) {
2341
- showNotification("Failed to generate reports", type="error", duration=5)
2342
  return(NULL)
2343
  }
2344
- zip::zip(zipfile=file, files=basename(pdfs), root=tmp); unlink(pdfs)
2345
- })
2346
- showNotification("✅ Coastal pitcher ZIP ready!", type="message", duration=5)
2347
- },
2348
- contentType = "application/zip"
2349
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2350
 
2351
  output$status_message <- renderUI({
2352
  if (input$report_type == "hitter") {
 
 
1
  library(shiny)
2
  library(dplyr)
3
  library(ggplot2)
 
1024
  )
1025
  }
1026
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1027
  create_pitcher_pitch_char <- function(game_data) {
1028
  game_data %>%
1029
  filter(TaggedPitchType != "Other", !is.na(TaggedPitchType)) %>%
 
1919
  }
1920
  })
1921
 
1922
+ output$download_pitcher <- downloadHandler(
1923
+ filename = function() {
1924
+ df <- data_pitcher(); req(df, input$pitcher_name)
1925
+ pitcher_clean <- gsub(" ", "_", input$pitcher_name)
1926
+ date_str <- format(parse_game_day(df %>% filter(Pitcher == input$pitcher_name)), "%Y%m%d")
1927
+ paste0(pitcher_clean, "_", date_str, "_Pitcher_Report.pdf")
1928
+ },
1929
+ content = function(file) {
1930
+ df <- data_pitcher(); req(df, input$pitcher_name)
1931
+ pitch_colors <- c("Fastball"="#FA8072","Four-Seam"="#FA8072","Sinker"="#fdae61",
1932
+ "Slider"="#A020F0","Sweeper"="magenta","Curveball"="#2c7bb6",
1933
+ "ChangeUp"="#90EE90","Splitter"="#90EE32","Cutter"="red")
1934
+ withProgress(message='Generating Pitcher PDF', value=0, {
1935
+ incProgress(.3, detail="Processing data...")
1936
+ incProgress(.4, detail="Creating visualizations...")
1937
+ create_pitcher_pdf(df, input$pitcher_name, file, pitch_colors)
1938
+ incProgress(.3, detail="Finalizing report...")
1939
+ })
1940
+ showNotification("✅ Pitcher report generated!", type="message", duration=3)
1941
+ },
1942
+ contentType = "application/pdf"
1943
+ )
1944
+
1945
+ # ---- Pitcher bulk ZIP
1946
+ output$download_all_coastal_pitchers <- downloadHandler(
1947
+ filename = function() {
1948
+ df <- data_pitcher(); req(df)
1949
+ paste0("Coastal_Pitcher_Reports_", format(parse_game_day(df), "%Y%m%d"), ".zip")
1950
+ },
1951
+ content = function(file) {
1952
+ df <- data_pitcher(); req(df)
1953
+ pitch_colors <- c(
1954
+ "Fastball"="#FA8072","Four-Seam"="#FA8072","Sinker"="#fdae61",
1955
+ "Slider"="#A020F0","Sweeper"="magenta","Curveball"="#2c7bb6",
1956
+ "ChangeUp"="#90EE90","Splitter"="#90EE32","Cutter"="red"
1957
+ )
1958
+ pitchers <- df %>% dplyr::filter(PitcherTeam == "COA_CHA") %>%
1959
+ dplyr::pull(Pitcher) %>% unique() %>% na.omit() %>% sort()
1960
+ if (!length(pitchers)) {
1961
+ showNotification("No Coastal pitchers found", type="error", duration=5)
 
 
 
 
 
 
 
1962
  return(NULL)
1963
  }
1964
+ withProgress(message='Generating Coastal Pitcher Reports', value=0, {
1965
+ tmp <- tempdir(); pdfs <- character(0); total <- length(pitchers)
1966
+ for (i in seq_along(pitchers)) {
1967
+ ply <- pitchers[i]; incProgress(1/total, detail=paste("Report for", ply))
1968
+ out <- file.path(tmp, paste0(gsub(" ","_",ply), "_",
1969
+ format(parse_game_day(df), "%Y%m%d"),
1970
+ "_Pitcher_Report.pdf"))
1971
+ try(create_pitcher_pdf(df, ply, out, pitch_colors), silent = TRUE)
1972
+ if (file.exists(out)) pdfs <- c(pdfs, out)
1973
+ }
1974
+ if (!length(pdfs)) {
1975
+ showNotification("Failed to generate reports", type="error", duration=5)
1976
+ return(NULL)
1977
+ }
1978
+ zip::zip(zipfile=file, files=basename(pdfs), root=tmp); unlink(pdfs)
1979
+ })
1980
+ showNotification("✅ Coastal pitcher ZIP ready!", type="message", duration=5)
1981
+ },
1982
+ contentType = "application/zip"
1983
+ )
1984
+
1985
 
1986
  output$status_message <- renderUI({
1987
  if (input$report_type == "hitter") {