igroffman commited on
Commit
94b13db
·
verified ·
1 Parent(s): 5a5be03

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +160 -107
app.R CHANGED
@@ -2941,6 +2941,37 @@ create_pitcher_pdf <- function(game_df, pitcher_name, output_file, pitch_colors)
2941
  invisible(output_file)
2942
  }
2943
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2944
  # =====================================================================
2945
  # ====================== UMPIRE CODE ================================
2946
  # =====================================================================
@@ -3013,6 +3044,30 @@ umpire_create_report_pdf <- function(data,
3013
  y = c( 0.150, 0.150, 0.300, 0.500, 0.300)
3014
  )
3015
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3016
  base_zone <- function() {
3017
  ggplot() +
3018
  geom_rect(data = buffer_zone_rect,
@@ -3039,85 +3094,82 @@ umpire_create_report_pdf <- function(data,
3039
  )
3040
  }
3041
 
3042
- umpire_create_ball_plot <- function(df, side_label, title_label) {
3043
- pts <- df %>%
 
 
 
3044
  filter(BatterSide == side_label,
3045
  PitchCall %in% c("BallCalled", "BallinDirt"),
3046
  PlateLocSide > -0.83083, PlateLocSide < 0.83083,
3047
  PlateLocHeight < 3.37750, PlateLocHeight > 1.5)
3048
  base_zone() +
3049
  geom_point(data = pts, aes(x = PlateLocSide, y = PlateLocHeight),
3050
- pch = 21, fill = "#006F71", color = "black", size = 2.5) +
 
 
3051
  ggtitle(title_label)
3052
  }
3053
 
3054
- umpire_create_strike_plot <- function(df, side_label, title_label) {
3055
- pts <- df %>%
3056
  filter(BatterSide == side_label, PitchCall == "StrikeCalled") %>%
3057
  filter(PlateLocSide < -0.83083 | PlateLocSide > 0.83083 |
3058
  PlateLocHeight > 3.37750 | PlateLocHeight < 1.5)
3059
  base_zone() +
3060
  geom_point(data = pts, aes(x = PlateLocSide, y = PlateLocHeight),
3061
- pch = 21, fill = "#006F71", color = "black", size = 2.5) +
 
 
3062
  ggtitle(title_label)
3063
  }
3064
 
3065
- umpire_create_ball_plot_team <- function(df, is_ccu, title_label) {
3066
- pts <- df %>%
3067
  filter(if (is_ccu) BatterTeam == "COA_CHA" else BatterTeam != "COA_CHA",
3068
  PitchCall %in% c("BallCalled", "BallinDirt"),
3069
  PlateLocSide > -0.83083, PlateLocSide < 0.83083,
3070
  PlateLocHeight < 3.37750, PlateLocHeight > 1.5)
3071
  base_zone() +
3072
  geom_point(data = pts, aes(x = PlateLocSide, y = PlateLocHeight),
3073
- pch = 21, fill = "#006F71", color = "black", size = 2.5) +
 
 
3074
  ggtitle(title_label)
3075
  }
3076
 
3077
- umpire_create_strike_plot_team <- function(df, is_ccu, title_label) {
3078
- pts <- df %>%
3079
  filter(if (is_ccu) BatterTeam == "COA_CHA" else BatterTeam != "COA_CHA",
3080
  PitchCall == "StrikeCalled") %>%
3081
  filter(PlateLocSide < -0.83083 | PlateLocSide > 0.83083 |
3082
  PlateLocHeight > 3.37750 | PlateLocHeight < 1.5)
3083
  base_zone() +
3084
  geom_point(data = pts, aes(x = PlateLocSide, y = PlateLocHeight),
3085
- pch = 21, fill = "#006F71", color = "black", size = 2.5) +
 
 
3086
  ggtitle(title_label)
3087
  }
3088
 
3089
- plot_ball_lhb <- umpire_create_ball_plot(data, "Left", "Ball Called v LHB")
3090
- plot_ball_rhb <- umpire_create_ball_plot(data, "Right", "Ball Called v RHB")
3091
- plot_strike_lhb <- umpire_create_strike_plot(data, "Left", "Strike Called v LHB")
3092
- plot_strike_rhb <- umpire_create_strike_plot(data, "Right", "Strike Called v RHB")
3093
-
3094
- plot_ball_ccu <- umpire_create_ball_plot_team(data, TRUE, "Ball Called v CCU Hitters")
3095
- plot_ball_opp <- umpire_create_ball_plot_team(data, FALSE, "Ball Called v Opp Hitters")
3096
- plot_strike_ccu <- umpire_create_strike_plot_team(data, TRUE, "Strike Called v CCU Hitters")
3097
- plot_strike_opp <- umpire_create_strike_plot_team(data, FALSE, "Strike Called v Opp Hitters")
3098
-
3099
- MissedCalls <- dplyr::bind_rows(
3100
- data %>% filter(BatterSide == "Left",
3101
- PitchCall %in% c("BallCalled", "BallinDirt"),
3102
- PlateLocSide > -0.83083, PlateLocSide < 0.83083,
3103
- PlateLocHeight < 3.37750, PlateLocHeight > 1.5),
3104
- data %>% filter(BatterSide == "Right",
3105
- PitchCall %in% c("BallCalled", "BallinDirt"),
3106
- PlateLocSide > -0.83083, PlateLocSide < 0.83083,
3107
- PlateLocHeight < 3.37750, PlateLocHeight > 1.5),
3108
- data %>% filter(BatterSide == "Left", PitchCall == "StrikeCalled") %>%
3109
- filter(PlateLocSide < -0.83083 | PlateLocSide > 0.83083 |
3110
- PlateLocHeight > 3.37750 | PlateLocHeight < 1.5),
3111
- data %>% filter(BatterSide == "Right", PitchCall == "StrikeCalled") %>%
3112
- filter(PlateLocSide < -0.83083 | PlateLocSide > 0.83083 |
3113
- PlateLocHeight > 3.37750 | PlateLocHeight < 1.5)
3114
- ) %>%
3115
- mutate(
3116
- Side = paste0(sprintf("%.0f", abs(PlateLocSide * 12)), '"'),
3117
- Height = paste0(sprintf("%.0f", PlateLocHeight * 12), '"')
3118
- ) %>%
3119
- arrange(PitchNo) %>%
3120
- select(dplyr::any_of(c("PitchNo","Inning","Top/Bottom","TopBottom",
3121
  "Batter","PitchCall","Side","Height","BatterTeam")))
3122
 
3123
  green <- "#006F71"
@@ -3150,67 +3202,67 @@ umpire_create_report_pdf <- function(data,
3150
  check.names = FALSE
3151
  )
3152
 
3153
- draw_header <- function() {
3154
-
3155
- suppressPackageStartupMessages({
3156
- library(grid)
3157
- library(magick)
3158
- })
3159
-
3160
- draw_logo_url <- function(url, x, just) {
3161
- img <- try(
3162
- magick::image_read(url),
3163
- silent = TRUE
3164
- )
3165
-
3166
- if (inherits(img, "try-error")) return(NULL)
3167
-
3168
- img <- magick::image_resize(img, "x130")
3169
-
3170
- grid.draw(
3171
- grid::rasterGrob(
3172
- as.raster(img),
3173
- interpolate = TRUE,
3174
- vp = viewport(
3175
- x = x,
3176
- y = 0.96,
3177
- width = 0.13,
3178
- height = 0.08,
3179
- just = c(just, "center")
 
3180
  )
3181
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3182
  )
3183
- }
3184
-
3185
- ## --- EMBEDDED LOGO LINKS ---
3186
- left_logo_url <- "https://i.imgur.com/zjTu3JS.png"
3187
- right_logo_url <- "https://i.ibb.co/Q3kFXXd9/8acd1b8a-7920-403a-8e9d-86742634effb.png"
3188
-
3189
- draw_logo_url(left_logo_url, 0.05, "left")
3190
- draw_logo_url(right_logo_url, 0.95, "right")
3191
-
3192
- ## --- HEADER TEXT ---
3193
- grid.text(
3194
- title_text,
3195
- y = 0.975,
3196
- gp = gpar(fontsize = 16, fontface = "bold")
3197
- )
3198
-
3199
- grid.text(
3200
- subhead_text,
3201
- y = 0.948,
3202
- gp = gpar(fontsize = 12, fontface = "bold")
3203
- )
3204
-
3205
- if (!is.na(game_date)) {
3206
  grid.text(
3207
- game_date,
3208
- y = 0.925,
3209
- gp = gpar(fontsize = 9)
3210
  )
 
 
 
 
 
 
 
 
3211
  }
3212
- }
3213
-
3214
  grDevices::pdf(output_file, width = 8.5, height = 11)
3215
 
3216
  grid.newpage()
@@ -3231,8 +3283,8 @@ draw_header <- function() {
3231
  popViewport()
3232
 
3233
  grid.newpage()
3234
-
3235
- grid::grid.text("Data: TrackMan | Report Generated: Coastal Carolina Baseball Analytics",
3236
  x = 0.5, y = 0.02, gp = grid::gpar(cex = 0.75, col = "grey50"))
3237
 
3238
  pushViewport(viewport(x = 0.28, y = 0.81, width = 0.47, height = 0.27)); print(plot_ball_ccu, newpage = FALSE); popViewport()
@@ -3240,25 +3292,25 @@ draw_header <- function() {
3240
  pushViewport(viewport(x = 0.28, y = 0.60, width = 0.47, height = 0.27)); print(plot_strike_ccu, newpage = FALSE); popViewport()
3241
  pushViewport(viewport(x = 0.72, y = 0.60, width = 0.47, height = 0.27)); print(plot_strike_opp, newpage = FALSE); popViewport()
3242
 
3243
- if (nrow(MissedCalls) > 0) {
3244
- # First page can fit 10 rows in the bottom half
3245
  first_page_rows <- 15
3246
  remaining_page_rows <- rows_per_page # 30 rows per full page
3247
 
3248
- if (nrow(MissedCalls) <= first_page_rows) {
3249
  # Fits on current page
3250
  pushViewport(viewport(x = 0.5, y = 0.30, width = 0.92, height = 0.50))
3251
- grid.table(MissedCalls, rows = NULL, theme = ttheme_green_small)
3252
  popViewport()
3253
  } else {
3254
  # First batch on current page
3255
- first_chunk <- MissedCalls[1:first_page_rows, , drop = FALSE]
3256
  pushViewport(viewport(x = 0.5, y = 0.30, width = 0.92, height = 0.50))
3257
  grid.table(first_chunk, rows = NULL, theme = ttheme_green_small)
3258
  popViewport()
3259
 
3260
  # Remaining rows paginated onto new pages
3261
- remaining <- MissedCalls[(first_page_rows + 1):nrow(MissedCalls), , drop = FALSE]
3262
  remaining_chunks <- split(remaining, ceiling(seq_len(nrow(remaining)) / remaining_page_rows))
3263
 
3264
  for (i in seq_along(remaining_chunks)) {
@@ -3276,6 +3328,7 @@ draw_header <- function() {
3276
  invisible(output_file)
3277
  }
3278
 
 
3279
  # Advanced Pitcher Functions
3280
  `%||%` <- function(a, b) if (!is.null(a)) a else b
3281
 
 
2941
  invisible(output_file)
2942
  }
2943
 
2944
+
2945
+ ## --- EMBEDDED LOGO LINKS ---
2946
+ left_logo_url <- "https://i.imgur.com/zjTu3JS.png"
2947
+ right_logo_url <- "https://i.ibb.co/Q3kFXXd9/8acd1b8a-7920-403a-8e9d-86742634effb.png"
2948
+
2949
+ draw_logo_url(left_logo_url, 0.05, "left")
2950
+ draw_logo_url(right_logo_url, 0.95, "right")
2951
+
2952
+ ## --- HEADER TEXT ---
2953
+ grid.text(
2954
+ title_text,
2955
+ y = 0.975,
2956
+ gp = gpar(fontsize = 16, fontface = "bold")
2957
+ )
2958
+
2959
+ grid.text(
2960
+ subhead_text,
2961
+ y = 0.948,
2962
+ gp = gpar(fontsize = 12, fontface = "bold")
2963
+ )
2964
+
2965
+ if (!is.na(game_date)) {
2966
+ grid.text(
2967
+ game_date,
2968
+ y = 0.925,
2969
+ gp = gpar(fontsize = 9)
2970
+ )
2971
+ }
2972
+ }
2973
+
2974
+
2975
  # =====================================================================
2976
  # ====================== UMPIRE CODE ================================
2977
  # =====================================================================
 
3044
  y = c( 0.150, 0.150, 0.300, 0.500, 0.300)
3045
  )
3046
 
3047
+ # ---------------------------------------------------------------
3048
+ # BUILD MISSED CALLS TABLE EARLY (before plots) so we can number them
3049
+ # ---------------------------------------------------------------
3050
+ MissedCalls <- dplyr::bind_rows(
3051
+ data %>% filter(PitchCall %in% c("BallCalled", "BallinDirt"),
3052
+ PlateLocSide > -0.83083, PlateLocSide < 0.83083,
3053
+ PlateLocHeight < 3.37750, PlateLocHeight > 1.5),
3054
+ data %>% filter(PitchCall == "StrikeCalled") %>%
3055
+ filter(PlateLocSide < -0.83083 | PlateLocSide > 0.83083 |
3056
+ PlateLocHeight > 3.37750 | PlateLocHeight < 1.5)
3057
+ ) %>%
3058
+ arrange(PitchNo) %>%
3059
+ mutate(CallNo = row_number()) %>%
3060
+ mutate(
3061
+ Side = paste0(sprintf("%.0f", abs(PlateLocSide * 12)), '"'),
3062
+ Height = paste0(sprintf("%.0f", PlateLocHeight * 12), '"')
3063
+ )
3064
+
3065
+ # Keep PlateLocSide/PlateLocHeight/BatterSide/BatterTeam available for plot filtering
3066
+ # but select display columns for the table at the end
3067
+
3068
+ # ---------------------------------------------------------------
3069
+ # BASE ZONE HELPER
3070
+ # ---------------------------------------------------------------
3071
  base_zone <- function() {
3072
  ggplot() +
3073
  geom_rect(data = buffer_zone_rect,
 
3094
  )
3095
  }
3096
 
3097
+ # ---------------------------------------------------------------
3098
+ # PLOT HELPERS — now use MissedCalls with CallNo labels
3099
+ # ---------------------------------------------------------------
3100
+ umpire_create_ball_plot <- function(mc, side_label, title_label) {
3101
+ pts <- mc %>%
3102
  filter(BatterSide == side_label,
3103
  PitchCall %in% c("BallCalled", "BallinDirt"),
3104
  PlateLocSide > -0.83083, PlateLocSide < 0.83083,
3105
  PlateLocHeight < 3.37750, PlateLocHeight > 1.5)
3106
  base_zone() +
3107
  geom_point(data = pts, aes(x = PlateLocSide, y = PlateLocHeight),
3108
+ pch = 21, fill = "#006F71", color = "black", size = 5) +
3109
+ geom_text(data = pts, aes(x = PlateLocSide, y = PlateLocHeight, label = CallNo),
3110
+ color = "white", size = 2.2, fontface = "bold") +
3111
  ggtitle(title_label)
3112
  }
3113
 
3114
+ umpire_create_strike_plot <- function(mc, side_label, title_label) {
3115
+ pts <- mc %>%
3116
  filter(BatterSide == side_label, PitchCall == "StrikeCalled") %>%
3117
  filter(PlateLocSide < -0.83083 | PlateLocSide > 0.83083 |
3118
  PlateLocHeight > 3.37750 | PlateLocHeight < 1.5)
3119
  base_zone() +
3120
  geom_point(data = pts, aes(x = PlateLocSide, y = PlateLocHeight),
3121
+ pch = 21, fill = "#006F71", color = "black", size = 5) +
3122
+ geom_text(data = pts, aes(x = PlateLocSide, y = PlateLocHeight, label = CallNo),
3123
+ color = "white", size = 2.2, fontface = "bold") +
3124
  ggtitle(title_label)
3125
  }
3126
 
3127
+ umpire_create_ball_plot_team <- function(mc, is_ccu, title_label) {
3128
+ pts <- mc %>%
3129
  filter(if (is_ccu) BatterTeam == "COA_CHA" else BatterTeam != "COA_CHA",
3130
  PitchCall %in% c("BallCalled", "BallinDirt"),
3131
  PlateLocSide > -0.83083, PlateLocSide < 0.83083,
3132
  PlateLocHeight < 3.37750, PlateLocHeight > 1.5)
3133
  base_zone() +
3134
  geom_point(data = pts, aes(x = PlateLocSide, y = PlateLocHeight),
3135
+ pch = 21, fill = "#006F71", color = "black", size = 5) +
3136
+ geom_text(data = pts, aes(x = PlateLocSide, y = PlateLocHeight, label = CallNo),
3137
+ color = "white", size = 2.2, fontface = "bold") +
3138
  ggtitle(title_label)
3139
  }
3140
 
3141
+ umpire_create_strike_plot_team <- function(mc, is_ccu, title_label) {
3142
+ pts <- mc %>%
3143
  filter(if (is_ccu) BatterTeam == "COA_CHA" else BatterTeam != "COA_CHA",
3144
  PitchCall == "StrikeCalled") %>%
3145
  filter(PlateLocSide < -0.83083 | PlateLocSide > 0.83083 |
3146
  PlateLocHeight > 3.37750 | PlateLocHeight < 1.5)
3147
  base_zone() +
3148
  geom_point(data = pts, aes(x = PlateLocSide, y = PlateLocHeight),
3149
+ pch = 21, fill = "#006F71", color = "black", size = 5) +
3150
+ geom_text(data = pts, aes(x = PlateLocSide, y = PlateLocHeight, label = CallNo),
3151
+ color = "white", size = 2.2, fontface = "bold") +
3152
  ggtitle(title_label)
3153
  }
3154
 
3155
+ # ---------------------------------------------------------------
3156
+ # CREATE ALL PLOTS (pass MissedCalls instead of data)
3157
+ # ---------------------------------------------------------------
3158
+ plot_ball_lhb <- umpire_create_ball_plot(MissedCalls, "Left", "Ball Called v LHB")
3159
+ plot_ball_rhb <- umpire_create_ball_plot(MissedCalls, "Right", "Ball Called v RHB")
3160
+ plot_strike_lhb <- umpire_create_strike_plot(MissedCalls, "Left", "Strike Called v LHB")
3161
+ plot_strike_rhb <- umpire_create_strike_plot(MissedCalls, "Right", "Strike Called v RHB")
3162
+
3163
+ plot_ball_ccu <- umpire_create_ball_plot_team(MissedCalls, TRUE, "Ball Called v CCU Hitters")
3164
+ plot_ball_opp <- umpire_create_ball_plot_team(MissedCalls, FALSE, "Ball Called v Opp Hitters")
3165
+ plot_strike_ccu <- umpire_create_strike_plot_team(MissedCalls, TRUE, "Strike Called v CCU Hitters")
3166
+ plot_strike_opp <- umpire_create_strike_plot_team(MissedCalls, FALSE, "Strike Called v Opp Hitters")
3167
+
3168
+ # ---------------------------------------------------------------
3169
+ # PREPARE DISPLAY TABLE (select only display columns, CallNo first)
3170
+ # ---------------------------------------------------------------
3171
+ MissedCallsDisplay <- MissedCalls %>%
3172
+ select(dplyr::any_of(c("CallNo","PitchNo","Inning","Top/Bottom","TopBottom",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3173
  "Batter","PitchCall","Side","Height","BatterTeam")))
3174
 
3175
  green <- "#006F71"
 
3202
  check.names = FALSE
3203
  )
3204
 
3205
+ draw_header <- function() {
3206
+
3207
+ suppressPackageStartupMessages({
3208
+ library(grid)
3209
+ library(magick)
3210
+ })
3211
+
3212
+ draw_logo_url <- function(url, x, just) {
3213
+ img <- try(
3214
+ magick::image_read(url),
3215
+ silent = TRUE
3216
+ )
3217
+
3218
+ if (inherits(img, "try-error")) return(NULL)
3219
+
3220
+ img <- magick::image_resize(img, "x130")
3221
+
3222
+ grid.draw(
3223
+ grid::rasterGrob(
3224
+ as.raster(img),
3225
+ interpolate = TRUE,
3226
+ vp = viewport(
3227
+ x = x,
3228
+ y = 0.96,
3229
+ width = 0.13,
3230
+ height = 0.08,
3231
+ just = c(just, "center")
3232
+ )
3233
  )
3234
  )
3235
+ }
3236
+
3237
+ ## --- EMBEDDED LOGO LINKS ---
3238
+ left_logo_url <- "https://i.imgur.com/zjTu3JS.png"
3239
+ right_logo_url <- "https://i.ibb.co/Q3kFXXd9/8acd1b8a-7920-403a-8e9d-86742634effb.png"
3240
+
3241
+ draw_logo_url(left_logo_url, 0.05, "left")
3242
+ draw_logo_url(right_logo_url, 0.95, "right")
3243
+
3244
+ ## --- HEADER TEXT ---
3245
+ grid.text(
3246
+ title_text,
3247
+ y = 0.975,
3248
+ gp = gpar(fontsize = 16, fontface = "bold")
3249
  )
3250
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3251
  grid.text(
3252
+ subhead_text,
3253
+ y = 0.948,
3254
+ gp = gpar(fontsize = 12, fontface = "bold")
3255
  )
3256
+
3257
+ if (!is.na(game_date)) {
3258
+ grid.text(
3259
+ game_date,
3260
+ y = 0.925,
3261
+ gp = gpar(fontsize = 9)
3262
+ )
3263
+ }
3264
  }
3265
+
 
3266
  grDevices::pdf(output_file, width = 8.5, height = 11)
3267
 
3268
  grid.newpage()
 
3283
  popViewport()
3284
 
3285
  grid.newpage()
3286
+
3287
+ grid::grid.text("Data: TrackMan | Report Generated: Coastal Carolina Baseball Analytics",
3288
  x = 0.5, y = 0.02, gp = grid::gpar(cex = 0.75, col = "grey50"))
3289
 
3290
  pushViewport(viewport(x = 0.28, y = 0.81, width = 0.47, height = 0.27)); print(plot_ball_ccu, newpage = FALSE); popViewport()
 
3292
  pushViewport(viewport(x = 0.28, y = 0.60, width = 0.47, height = 0.27)); print(plot_strike_ccu, newpage = FALSE); popViewport()
3293
  pushViewport(viewport(x = 0.72, y = 0.60, width = 0.47, height = 0.27)); print(plot_strike_opp, newpage = FALSE); popViewport()
3294
 
3295
+ if (nrow(MissedCallsDisplay) > 0) {
3296
+ # First page can fit 15 rows in the bottom half
3297
  first_page_rows <- 15
3298
  remaining_page_rows <- rows_per_page # 30 rows per full page
3299
 
3300
+ if (nrow(MissedCallsDisplay) <= first_page_rows) {
3301
  # Fits on current page
3302
  pushViewport(viewport(x = 0.5, y = 0.30, width = 0.92, height = 0.50))
3303
+ grid.table(MissedCallsDisplay, rows = NULL, theme = ttheme_green_small)
3304
  popViewport()
3305
  } else {
3306
  # First batch on current page
3307
+ first_chunk <- MissedCallsDisplay[1:first_page_rows, , drop = FALSE]
3308
  pushViewport(viewport(x = 0.5, y = 0.30, width = 0.92, height = 0.50))
3309
  grid.table(first_chunk, rows = NULL, theme = ttheme_green_small)
3310
  popViewport()
3311
 
3312
  # Remaining rows paginated onto new pages
3313
+ remaining <- MissedCallsDisplay[(first_page_rows + 1):nrow(MissedCallsDisplay), , drop = FALSE]
3314
  remaining_chunks <- split(remaining, ceiling(seq_len(nrow(remaining)) / remaining_page_rows))
3315
 
3316
  for (i in seq_along(remaining_chunks)) {
 
3328
  invisible(output_file)
3329
  }
3330
 
3331
+
3332
  # Advanced Pitcher Functions
3333
  `%||%` <- function(a, b) if (!is.null(a)) a else b
3334