igroffman commited on
Commit
16e3e5d
·
verified ·
1 Parent(s): e57522e

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +110 -6
app.R CHANGED
@@ -1031,11 +1031,12 @@ create_bp_contact_map <- function(batter_name, bp_data) {
1031
  }
1032
 
1033
  create_bp_contact_side <- function(batter_name, bp_data) {
 
1034
  contact_data <- bp_data %>%
1035
  filter(Batter == batter_name, BIPind == 1, !is.na(ExitSpeed),
1036
- !is.na(ContactPositionY), !is.na(ContactPositionZ))
1037
 
1038
- # Home plate side illustration (tip toward catcher at Y = 0, front edge at 1.417 ft)
1039
  plate_df <- data.frame(
1040
  x = c(0, 0.708, 1.417, 1.417, 0.708),
1041
  y = c(0.25, 0.35, 0.35, 0.15, 0.15)
@@ -1061,14 +1062,14 @@ create_bp_contact_side <- function(batter_name, bp_data) {
1061
  ggplot() +
1062
  geom_polygon(data = plate_df, aes(x, y), fill = NA, color = "gray70", linewidth = 0.5) +
1063
  coord_fixed(xlim = c(-1, 4), ylim = c(0, 5), expand = FALSE) +
1064
- labs(x = "Contact Pos Y (in feet)", y = "Contact Pos Z (in feet)") +
1065
  ggtitle(paste("Contact Depth vs Height:", batter_name)) +
1066
  annotate("text", x = 1.5, y = 2.5, label = "No contact data available", size = 5, color = "gray50") +
1067
  base_theme
1068
  )
1069
  }
1070
 
1071
- ggplot(contact_data, aes(x = ContactPositionY, y = ContactPositionZ)) +
1072
  geom_polygon(data = plate_df, aes(x, y), fill = "white", color = "black",
1073
  linewidth = 0.6, inherit.aes = FALSE) +
1074
  geom_point(aes(fill = ExitSpeed), size = 3, shape = 21, color = "black", stroke = 0.4, alpha = 0.85) +
@@ -1076,7 +1077,7 @@ create_bp_contact_side <- function(batter_name, bp_data) {
1076
  midpoint = mean(contact_data$ExitSpeed, na.rm = TRUE),
1077
  name = "Exit Velo") +
1078
  coord_fixed(xlim = c(-1, 4), ylim = c(0, 5), expand = FALSE) +
1079
- labs(x = "Contact Pos Y (in feet)", y = "Contact Pos Z (in feet)") +
1080
  ggtitle(paste("Contact Depth vs Height:", batter_name)) +
1081
  base_theme
1082
  }
@@ -1117,6 +1118,76 @@ bp_leaderboard_colors <- function(x, target = NULL) {
1117
  cols
1118
  }
1119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1120
  create_bp_pdf <- function(bp_data, batter_name, output_file) {
1121
  if (length(dev.list()) > 0) try(dev.off(), silent = TRUE)
1122
 
@@ -7087,6 +7158,36 @@ server <- function(input, output, session) {
7087
  contentType = "application/zip"
7088
  )
7089
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7090
  output$download_all_coastal_pitchers <- downloadHandler(
7091
  filename = function() {
7092
  df <- data_pitcher(); req(df)
@@ -7387,6 +7488,8 @@ server <- function(input, output, session) {
7387
  choices = c(date_choices, "All Dates"),
7388
  selected = date_choices[1], width = "220px"),
7389
  DT::dataTableOutput("bp_leaderboard"),
 
 
7390
  br(),
7391
  h4("BP Spray Chart", style = "color: #006F71;"),
7392
  plotOutput("preview_bp_spray", height = "400px"),
@@ -7441,7 +7544,8 @@ server <- function(input, output, session) {
7441
 
7442
  output$bp_leaderboard <- DT::renderDataTable({
7443
  lb <- bp_leaderboard_data()
7444
- validate(need(nrow(lb) > 0, "No BP data for this date"))
 
7445
 
7446
  # Red = bad, white = group average, green = good (Avg LA scored by closeness to 20 deg)
7447
  # BBE stays uncolored - volume, not quality
 
1031
  }
1032
 
1033
  create_bp_contact_side <- function(batter_name, bp_data) {
1034
+ # TrackMan contact coords: X = depth (toward pitcher), Y = height, Z = side
1035
  contact_data <- bp_data %>%
1036
  filter(Batter == batter_name, BIPind == 1, !is.na(ExitSpeed),
1037
+ !is.na(ContactPositionX), !is.na(ContactPositionY))
1038
 
1039
+ # Home plate side illustration (tip toward catcher at depth 0, front edge at 1.417 ft)
1040
  plate_df <- data.frame(
1041
  x = c(0, 0.708, 1.417, 1.417, 0.708),
1042
  y = c(0.25, 0.35, 0.35, 0.15, 0.15)
 
1062
  ggplot() +
1063
  geom_polygon(data = plate_df, aes(x, y), fill = NA, color = "gray70", linewidth = 0.5) +
1064
  coord_fixed(xlim = c(-1, 4), ylim = c(0, 5), expand = FALSE) +
1065
+ labs(x = "Contact Depth (ft in front of plate)", y = "Contact Height (ft)") +
1066
  ggtitle(paste("Contact Depth vs Height:", batter_name)) +
1067
  annotate("text", x = 1.5, y = 2.5, label = "No contact data available", size = 5, color = "gray50") +
1068
  base_theme
1069
  )
1070
  }
1071
 
1072
+ ggplot(contact_data, aes(x = ContactPositionX, y = ContactPositionY)) +
1073
  geom_polygon(data = plate_df, aes(x, y), fill = "white", color = "black",
1074
  linewidth = 0.6, inherit.aes = FALSE) +
1075
  geom_point(aes(fill = ExitSpeed), size = 3, shape = 21, color = "black", stroke = 0.4, alpha = 0.85) +
 
1077
  midpoint = mean(contact_data$ExitSpeed, na.rm = TRUE),
1078
  name = "Exit Velo") +
1079
  coord_fixed(xlim = c(-1, 4), ylim = c(0, 5), expand = FALSE) +
1080
+ labs(x = "Contact Depth (ft in front of plate)", y = "Contact Height (ft)") +
1081
  ggtitle(paste("Contact Depth vs Height:", batter_name)) +
1082
  base_theme
1083
  }
 
1118
  cols
1119
  }
1120
 
1121
+ create_bp_leaderboard_pdf <- function(lb, date_label, output_file) {
1122
+ if (length(dev.list()) > 0) try(dev.off(), silent = TRUE)
1123
+
1124
+ stat_cols <- c("BBE", "Avg EV", "Avg LA", "Max EV", "SC%", "10-30%", "HH%", "Barrel%")
1125
+ color_cols <- setdiff(stat_cols, "BBE") # BBE = volume, not quality
1126
+
1127
+ cell_fill <- matrix("white", nrow = nrow(lb), ncol = length(stat_cols),
1128
+ dimnames = list(NULL, stat_cols))
1129
+ for (cn in color_cols) {
1130
+ cell_fill[, cn] <- if (cn == "Avg LA") bp_leaderboard_colors(lb[[cn]], target = 20)
1131
+ else bp_leaderboard_colors(lb[[cn]])
1132
+ }
1133
+
1134
+ pdf(output_file, width = 11, height = 8.5)
1135
+ on.exit(try(dev.off(), silent = TRUE), add = TRUE)
1136
+
1137
+ name_w <- 0.20
1138
+ col_w <- 0.082
1139
+ row_h <- 0.028
1140
+ x0 <- 0.5 - (name_w + length(stat_cols) * col_w) / 2
1141
+ rows_per_page <- 25
1142
+ n_pages <- max(1, ceiling(nrow(lb) / rows_per_page))
1143
+
1144
+ draw_cell <- function(x, y, w, fill, label, bold = FALSE, txt_col = "black") {
1145
+ grid::grid.rect(x = x, y = y, width = w * 0.99, height = row_h,
1146
+ just = c("left", "top"),
1147
+ gp = grid::gpar(fill = fill, col = "black", lwd = 0.4))
1148
+ grid::grid.text(label, x = x + w * 0.5, y = y - row_h / 2,
1149
+ gp = grid::gpar(cex = 0.85, col = txt_col,
1150
+ fontface = if (bold) "bold" else "plain"))
1151
+ }
1152
+
1153
+ for (pg in seq_len(n_pages)) {
1154
+ grid::grid.newpage()
1155
+ grid::grid.text("Daily BP Leaderboard", y = 0.955,
1156
+ gp = grid::gpar(fontface = "bold", cex = 1.5, col = "#006F71"))
1157
+ if (!is.null(date_label)) {
1158
+ grid::grid.text(date_label, y = 0.915, gp = grid::gpar(cex = 1.1, col = "grey30"))
1159
+ }
1160
+ if (n_pages > 1) {
1161
+ grid::grid.text(paste0("Page ", pg, " of ", n_pages), x = 0.95, y = 0.03,
1162
+ gp = grid::gpar(cex = 0.7, col = "grey50"))
1163
+ }
1164
+
1165
+ y_top <- 0.86
1166
+
1167
+ # Header row
1168
+ draw_cell(x0, y_top, name_w, "#006F71", "Player", bold = TRUE, txt_col = "white")
1169
+ for (j in seq_along(stat_cols)) {
1170
+ draw_cell(x0 + name_w + (j - 1) * col_w, y_top, col_w, "#006F71",
1171
+ stat_cols[j], bold = TRUE, txt_col = "white")
1172
+ }
1173
+
1174
+ idx <- (((pg - 1) * rows_per_page) + 1):min(pg * rows_per_page, nrow(lb))
1175
+ for (r in seq_along(idx)) {
1176
+ i <- idx[r]
1177
+ y_r <- y_top - r * row_h
1178
+ draw_cell(x0, y_r, name_w, "white", lb$Batter[i], bold = TRUE)
1179
+ for (j in seq_along(stat_cols)) {
1180
+ val <- lb[[stat_cols[j]]][i]
1181
+ draw_cell(x0 + name_w + (j - 1) * col_w, y_r, col_w,
1182
+ cell_fill[i, stat_cols[j]],
1183
+ ifelse(is.finite(val), as.character(val), "-"))
1184
+ }
1185
+ }
1186
+ }
1187
+
1188
+ invisible(output_file)
1189
+ }
1190
+
1191
  create_bp_pdf <- function(bp_data, batter_name, output_file) {
1192
  if (length(dev.list()) > 0) try(dev.off(), silent = TRUE)
1193
 
 
7158
  contentType = "application/zip"
7159
  )
7160
 
7161
+ output$download_bp_leaderboard <- downloadHandler(
7162
+ filename = function() {
7163
+ sel <- input$bp_leaderboard_date
7164
+ date_str <- if (!is.null(sel) && sel != "All Dates") {
7165
+ gsub("-", "", sel)
7166
+ } else if (is.null(sel)) {
7167
+ df <- data_bp(); req(df)
7168
+ tryCatch(format(parse_game_day(df), "%Y%m%d"),
7169
+ error = function(e) format(Sys.Date(), "%Y%m%d"))
7170
+ } else "All_Dates"
7171
+ paste0("BP_Leaderboard_", date_str, ".pdf")
7172
+ },
7173
+ content = function(file) {
7174
+ lb <- bp_leaderboard_data()
7175
+ req(nrow(lb) > 0)
7176
+ sel <- input$bp_leaderboard_date
7177
+ date_label <- if (!is.null(sel) && sel != "All Dates") {
7178
+ format(as.Date(sel), "%B %e, %Y")
7179
+ } else if (is.null(sel)) {
7180
+ df <- data_bp()
7181
+ tryCatch(format(parse_game_day(df), "%B %e, %Y"), error = function(e) NULL)
7182
+ } else "All Dates"
7183
+ withProgress(message='Generating Leaderboard PDF', value=0.5, {
7184
+ create_bp_leaderboard_pdf(lb, date_label, file)
7185
+ })
7186
+ showNotification("Leaderboard PDF ready!", type="message", duration=3)
7187
+ },
7188
+ contentType = "application/pdf"
7189
+ )
7190
+
7191
  output$download_all_coastal_pitchers <- downloadHandler(
7192
  filename = function() {
7193
  df <- data_pitcher(); req(df)
 
7488
  choices = c(date_choices, "All Dates"),
7489
  selected = date_choices[1], width = "220px"),
7490
  DT::dataTableOutput("bp_leaderboard"),
7491
+ div(style = "margin-top:8px;",
7492
+ downloadButton("download_bp_leaderboard", "Download Leaderboard PDF", class = "btn-secondary")),
7493
  br(),
7494
  h4("BP Spray Chart", style = "color: #006F71;"),
7495
  plotOutput("preview_bp_spray", height = "400px"),
 
7544
 
7545
  output$bp_leaderboard <- DT::renderDataTable({
7546
  lb <- bp_leaderboard_data()
7547
+ # jsonlite (loaded after shiny) masks shiny::validate, so namespace explicitly
7548
+ shiny::validate(shiny::need(nrow(lb) > 0, "No BP data for this date"))
7549
 
7550
  # Red = bad, white = group average, green = good (Avg LA scored by closeness to 20 deg)
7551
  # BBE stays uncolored - volume, not quality