igroffman commited on
Commit
468cc00
·
verified ·
1 Parent(s): b244a6d

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +104 -30
app.R CHANGED
@@ -3011,13 +3011,67 @@ create_location_by_side_plot <- function(data, player_name, batter_side, pitch_c
3011
  create_location_by_result_plot(data, player_name, batter_side, pitch_colors)
3012
  }
3013
 
3014
- create_advanced_pitcher_pdf <- function(game_df, pitcher_name, output_file) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3015
  if (length(dev.list()) > 0) try(dev.off(), silent = TRUE)
3016
 
3017
  pitch_colors <- c(
3018
- "Fastball" = "#FA8072", "FourSeamFastBall" = "#FA8072", "Four-Seam" = "#FA8072", "Sinker" = "#fdae61",
3019
- "Slider" = "#A020F0", "Sweeper" = "magenta", "Curveball" = "#2c7bb6",
3020
- "ChangeUp" = "#90EE90", "Splitter" = "#90EE32", "Cutter" = "red"
 
3021
  )
3022
 
3023
  .text_on_fill <- function(hex) {
@@ -3029,7 +3083,6 @@ create_advanced_pitcher_pdf <- function(game_df, pitcher_name, output_file) {
3029
  }, error = function(e) "black")
3030
  }
3031
 
3032
- # Safe cell accessor - handles missing columns gracefully
3033
  get_cell_value <- function(df, colname, row_idx) {
3034
  if (is.null(df) || !is.data.frame(df)) return(NA)
3035
  if (is.null(colname) || !nzchar(colname)) return(NA)
@@ -3048,9 +3101,24 @@ create_advanced_pitcher_pdf <- function(game_df, pitcher_name, output_file) {
3048
  return(output_file)
3049
  }
3050
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3051
  game_day <- tryCatch(parse_game_day(pitcher_df), error = function(e) Sys.Date())
3052
 
3053
- # Get summary stats with error handling
3054
  summary_result <- tryCatch(
3055
  create_advanced_pitcher_summary(pitcher_df, pitcher_name),
3056
  error = function(e) {
@@ -3062,7 +3130,7 @@ create_advanced_pitcher_pdf <- function(game_df, pitcher_name, output_file) {
3062
  summary_stats <- summary_result$stats
3063
  summary_colors <- summary_result$colors
3064
 
3065
- # Get pitch characteristics with error handling
3066
  pitch_result <- tryCatch(
3067
  create_advanced_pitch_characteristics(pitcher_df, pitcher_name),
3068
  error = function(e) {
@@ -3076,7 +3144,7 @@ create_advanced_pitcher_pdf <- function(game_df, pitcher_name, output_file) {
3076
  pitch_char <- pitch_result$stats
3077
  pitch_colors_matrix <- pitch_result$colors
3078
 
3079
- # Handle empty or NULL pitch_char
3080
  if (is.null(pitch_char) || nrow(pitch_char) == 0) {
3081
  pitch_char <- data.frame(
3082
  Pitch = "-", Count = 0, `Usage%` = NA_real_,
@@ -3093,17 +3161,16 @@ create_advanced_pitcher_pdf <- function(game_df, pitcher_name, output_file) {
3093
  pitch_colors_matrix <- matrix("#FFFFFF", nrow = 1, ncol = ncol(pitch_char))
3094
  }
3095
 
3096
- # Limit rows to max 9
3097
  max_rows_to_show <- min(nrow(pitch_char), 9)
3098
  if (nrow(pitch_char) > max_rows_to_show) {
3099
  pitch_char <- pitch_char[1:max_rows_to_show, , drop = FALSE]
3100
  }
3101
 
3102
- # Get final dimensions
3103
  num_rows <- nrow(pitch_char)
3104
  num_cols <- ncol(pitch_char)
3105
 
3106
- # CRITICAL: Rebuild color matrix to EXACTLY match pitch_char dimensions
3107
  new_color_matrix <- matrix("#FFFFFF", nrow = num_rows, ncol = num_cols)
3108
  if (!is.null(pitch_colors_matrix) && is.matrix(pitch_colors_matrix)) {
3109
  rows_to_copy <- min(nrow(pitch_colors_matrix), num_rows)
@@ -3115,7 +3182,7 @@ create_advanced_pitcher_pdf <- function(game_df, pitcher_name, output_file) {
3115
  }
3116
  pitch_colors_matrix <- new_color_matrix
3117
 
3118
- # Create plots with error handling
3119
  movement_plot <- tryCatch(
3120
  create_movement_plot(pitcher_df, pitcher_name, pitch_colors),
3121
  error = function(e) ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::ggtitle("Movement Plot Error")
@@ -3136,11 +3203,8 @@ create_advanced_pitcher_pdf <- function(game_df, pitcher_name, output_file) {
3136
  create_count_usage_plot(pitcher_df, pitcher_name, pitch_colors),
3137
  error = function(e) ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::ggtitle("Count Usage Error")
3138
  )
3139
- extension_height_plot <- tryCatch(
3140
- create_extension_height_plot(pitcher_df, pitcher_name, pitch_colors),
3141
- error = function(e) ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::ggtitle("Extension/Height Error")
3142
- )
3143
 
 
3144
  relside_height_plot <- tryCatch(
3145
  create_relside_height_plot(pitcher_df, pitcher_name, pitch_colors),
3146
  error = function(e) ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::ggtitle("RelSide/Height Error")
@@ -3175,14 +3239,28 @@ create_advanced_pitcher_pdf <- function(game_df, pitcher_name, output_file) {
3175
  available_for_table <- y_top_char - (base_loc_top + table_margin)
3176
  row_h_char <- min(max_row_h, max(min_row_h, available_for_table / max(1, rows_including_header)))
3177
 
3178
- # Header
3179
- grid::pushViewport(grid::viewport(x = 0.5, y = header_y_top, width = 1, height = 0.04, just = c("center","top")))
 
 
 
 
 
 
 
3180
  grid::grid.text(paste(pitcher_name, "- Advanced Pitcher Report"),
 
3181
  gp = grid::gpar(fontface = "bold", cex = 1.8, col = "#006F71"))
 
 
 
 
 
 
3182
  grid::popViewport()
3183
 
3184
  # Summary section
3185
- grid::grid.text("Summary", x = 0.5, y = 0.94,
3186
  gp = grid::gpar(fontface = "bold", cex = 1.1, col = "#006F71"))
3187
 
3188
  summary_headers <- names(summary_stats)
@@ -3191,7 +3269,7 @@ create_advanced_pitcher_pdf <- function(game_df, pitcher_name, output_file) {
3191
 
3192
  x_start <- 0.5 - sum(summary_widths)/2
3193
  x_pos <- c(x_start, x_start + cumsum(summary_widths[-length(summary_widths)]))
3194
- y_top <- 0.925
3195
  row_h <- 0.020
3196
 
3197
  for (i in seq_along(summary_headers)) {
@@ -3209,6 +3287,7 @@ create_advanced_pitcher_pdf <- function(game_df, pitcher_name, output_file) {
3209
  gp = grid::gpar(cex = 0.62))
3210
  }
3211
 
 
3212
  grid::pushViewport(grid::viewport(x = 0.25, y = charts_y_top, width = 0.45, height = charts_height, just = c("center","top")))
3213
  tryCatch(print(movement_plot, newpage = FALSE), error = function(e) NULL)
3214
  grid::popViewport()
@@ -3217,16 +3296,16 @@ create_advanced_pitcher_pdf <- function(game_df, pitcher_name, output_file) {
3217
  tryCatch(print(velo_plot, newpage = FALSE), error = function(e) NULL)
3218
  grid::popViewport()
3219
 
3220
- # Row 2: Count plot (left) | Release charts stacked (right)
3221
- # Count plot - moved to left side, narrower
3222
- grid::pushViewport(grid::viewport(x = 0.27, y = count_y_top, width = 0.50, height = 18, just = c("center","top")))
3223
  tryCatch(print(count_plot, newpage = FALSE), error = function(e) NULL)
3224
  grid::popViewport()
3225
 
3226
- grid::pushViewport(grid::viewport(x = 0.77, y = count_y_top - count_height * 0.55, width = 0.42, height = count_height * 0.55, just = c("center","top")))
 
3227
  tryCatch(print(relside_height_plot, newpage = FALSE), error = function(e) NULL)
3228
  grid::popViewport()
3229
-
3230
 
3231
  # Pitch Characteristics table
3232
  grid::grid.text("Pitch Characteristics", x = 0.5, y = y_top_char + 0.015,
@@ -3235,7 +3314,6 @@ grid::pushViewport(grid::viewport(x = 0.77, y = count_y_top - count_height * 0.5
3235
  char_headers <- names(pitch_char)
3236
  num_char_cols <- length(char_headers)
3237
 
3238
- # Calculate column widths safely
3239
  if (num_char_cols > 1) {
3240
  char_widths <- c(0.10, rep((1 - 0.10 - 0.06) / (num_char_cols - 1), num_char_cols - 1))
3241
  } else {
@@ -3258,7 +3336,6 @@ grid::pushViewport(grid::viewport(x = 0.77, y = count_y_top - count_height * 0.5
3258
  gp = grid::gpar(col = "white", cex = 0.50, fontface = "bold"))
3259
  }
3260
 
3261
- # Find Pitch column index
3262
  i_col_pitch <- match("Pitch", char_headers)
3263
  has_pitchcol <- !is.na(i_col_pitch) && i_col_pitch >= 1
3264
 
@@ -3273,14 +3350,12 @@ grid::pushViewport(grid::viewport(x = 0.77, y = count_y_top - count_height * 0.5
3273
 
3274
  colname <- char_headers[i]
3275
 
3276
- # Get background color safely - check bounds explicitly
3277
  bg <- "#FFFFFF"
3278
  if (r >= 1 && r <= nrow(pitch_colors_matrix) && i >= 1 && i <= ncol(pitch_colors_matrix)) {
3279
  bg <- pitch_colors_matrix[r, i]
3280
  if (is.na(bg) || !nzchar(bg)) bg <- "#FFFFFF"
3281
  }
3282
 
3283
- # Override for Pitch column with pitch type color
3284
  if (has_pitchcol && identical(colname, "Pitch") && !is.na(pitch_name) && pitch_name %in% names(pitch_colors)) {
3285
  bg <- pitch_colors[[pitch_name]]
3286
  }
@@ -3288,7 +3363,6 @@ grid::pushViewport(grid::viewport(x = 0.77, y = count_y_top - count_height * 0.5
3288
  grid::grid.rect(x = x_pos_char[i], y = y_row, width = char_widths[i]*0.985, height = row_h_char,
3289
  just = c("left","top"), gp = grid::gpar(fill = bg, col = "grey80", lwd = 0.3))
3290
 
3291
- # Get value safely
3292
  val <- get_cell_value(pitch_char, colname, r)
3293
 
3294
  display_val <- if (is.numeric(val)) {
 
3011
  create_location_by_result_plot(data, player_name, batter_side, pitch_colors)
3012
  }
3013
 
3014
+ get_team_logo_path <- function(team_name, logo_dir = "logos") {
3015
+ if (is.null(team_name) || is.na(team_name) || !nzchar(team_name)) return(NULL)
3016
+
3017
+ # Normalize team name for file matching
3018
+ team_clean <- tolower(gsub("[^a-zA-Z0-9]", "_", team_name))
3019
+ team_nospace <- tolower(gsub("[^a-zA-Z0-9]", "", team_name))
3020
+
3021
+ # Check multiple possible paths
3022
+ possible_paths <- c(
3023
+ file.path(logo_dir, paste0(team_clean, ".png")),
3024
+ file.path(logo_dir, paste0(team_nospace, ".png")),
3025
+ file.path(logo_dir, paste0(team_name, ".png")),
3026
+ file.path(logo_dir, paste0(tolower(team_name), ".png")),
3027
+ # Common abbreviations
3028
+ file.path(logo_dir, "coastal_carolina.png"),
3029
+ file.path(logo_dir, "ccu.png")
3030
+ )
3031
+
3032
+ for (path in possible_paths) {
3033
+ if (file.exists(path)) {
3034
+ return(path)
3035
+ }
3036
+ }
3037
+
3038
+ return(NULL)
3039
+ }
3040
+
3041
+ # Function to add logo to the report
3042
+ add_team_logo <- function(logo_path, x, y, width, height) {
3043
+ if (is.null(logo_path) || !file.exists(logo_path)) {
3044
+ return(invisible(NULL))
3045
+ }
3046
+
3047
+ tryCatch({
3048
+ # Read the PNG image
3049
+ img <- png::readPNG(logo_path)
3050
+
3051
+ # Create a raster grob and draw it
3052
+ grid::grid.raster(
3053
+ img,
3054
+ x = x,
3055
+ y = y,
3056
+ width = width,
3057
+ height = height,
3058
+ just = c("center", "center")
3059
+ )
3060
+ }, error = function(e) {
3061
+ message("Could not load logo: ", e$message)
3062
+ invisible(NULL)
3063
+ })
3064
+ }
3065
+
3066
+
3067
+ create_advanced_pitcher_pdf <- function(game_df, pitcher_name, output_file, logo_dir = "logos") {
3068
  if (length(dev.list()) > 0) try(dev.off(), silent = TRUE)
3069
 
3070
  pitch_colors <- c(
3071
+ "Fastball" = "#FA8072", "FourSeamFastBall" = "#FA8072", "Four-Seam" = "#FA8072",
3072
+ "Sinker" = "#fdae61", "Slider" = "#A020F0", "Sweeper" = "magenta",
3073
+ "Curveball" = "#2c7bb6", "ChangeUp" = "#90EE90", "Splitter" = "#90EE32",
3074
+ "Cutter" = "red"
3075
  )
3076
 
3077
  .text_on_fill <- function(hex) {
 
3083
  }, error = function(e) "black")
3084
  }
3085
 
 
3086
  get_cell_value <- function(df, colname, row_idx) {
3087
  if (is.null(df) || !is.data.frame(df)) return(NA)
3088
  if (is.null(colname) || !nzchar(colname)) return(NA)
 
3101
  return(output_file)
3102
  }
3103
 
3104
+ # Get pitcher's team for logo
3105
+ pitcher_team <- NULL
3106
+ if ("PitcherTeam" %in% names(pitcher_df)) {
3107
+ pitcher_team <- pitcher_df$PitcherTeam[1]
3108
+ } else if ("Team" %in% names(pitcher_df)) {
3109
+ pitcher_team <- pitcher_df$Team[1]
3110
+ } else if ("HomeTeam" %in% names(pitcher_df)) {
3111
+ # Try to determine team from context
3112
+ pitcher_team <- pitcher_df$HomeTeam[1]
3113
+ }
3114
+
3115
+ # Get logo path
3116
+
3117
+ logo_path <- get_team_logo_path(pitcher_team, logo_dir)
3118
+
3119
  game_day <- tryCatch(parse_game_day(pitcher_df), error = function(e) Sys.Date())
3120
 
3121
+ # Get summary stats
3122
  summary_result <- tryCatch(
3123
  create_advanced_pitcher_summary(pitcher_df, pitcher_name),
3124
  error = function(e) {
 
3130
  summary_stats <- summary_result$stats
3131
  summary_colors <- summary_result$colors
3132
 
3133
+ # Get pitch characteristics
3134
  pitch_result <- tryCatch(
3135
  create_advanced_pitch_characteristics(pitcher_df, pitcher_name),
3136
  error = function(e) {
 
3144
  pitch_char <- pitch_result$stats
3145
  pitch_colors_matrix <- pitch_result$colors
3146
 
3147
+ # Handle empty pitch_char
3148
  if (is.null(pitch_char) || nrow(pitch_char) == 0) {
3149
  pitch_char <- data.frame(
3150
  Pitch = "-", Count = 0, `Usage%` = NA_real_,
 
3161
  pitch_colors_matrix <- matrix("#FFFFFF", nrow = 1, ncol = ncol(pitch_char))
3162
  }
3163
 
3164
+ # Limit rows
3165
  max_rows_to_show <- min(nrow(pitch_char), 9)
3166
  if (nrow(pitch_char) > max_rows_to_show) {
3167
  pitch_char <- pitch_char[1:max_rows_to_show, , drop = FALSE]
3168
  }
3169
 
 
3170
  num_rows <- nrow(pitch_char)
3171
  num_cols <- ncol(pitch_char)
3172
 
3173
+ # Rebuild color matrix
3174
  new_color_matrix <- matrix("#FFFFFF", nrow = num_rows, ncol = num_cols)
3175
  if (!is.null(pitch_colors_matrix) && is.matrix(pitch_colors_matrix)) {
3176
  rows_to_copy <- min(nrow(pitch_colors_matrix), num_rows)
 
3182
  }
3183
  pitch_colors_matrix <- new_color_matrix
3184
 
3185
+ # Create plots
3186
  movement_plot <- tryCatch(
3187
  create_movement_plot(pitcher_df, pitcher_name, pitch_colors),
3188
  error = function(e) ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::ggtitle("Movement Plot Error")
 
3203
  create_count_usage_plot(pitcher_df, pitcher_name, pitch_colors),
3204
  error = function(e) ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::ggtitle("Count Usage Error")
3205
  )
 
 
 
 
3206
 
3207
+ # ENLARGED release side plot
3208
  relside_height_plot <- tryCatch(
3209
  create_relside_height_plot(pitcher_df, pitcher_name, pitch_colors),
3210
  error = function(e) ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::ggtitle("RelSide/Height Error")
 
3239
  available_for_table <- y_top_char - (base_loc_top + table_margin)
3240
  row_h_char <- min(max_row_h, max(min_row_h, available_for_table / max(1, rows_including_header)))
3241
 
3242
+ # ===== HEADER WITH LOGO =====
3243
+ grid::pushViewport(grid::viewport(x = 0.5, y = header_y_top, width = 1, height = 0.06, just = c("center","top")))
3244
+
3245
+ # Add team logo on the left if available
3246
+ if (!is.null(logo_path) && file.exists(logo_path)) {
3247
+ add_team_logo(logo_path, x = 0.08, y = 0.5, width = 0.06, height = grid::unit(0.8, "npc"))
3248
+ }
3249
+
3250
+ # Title in center
3251
  grid::grid.text(paste(pitcher_name, "- Advanced Pitcher Report"),
3252
+ x = 0.5, y = 0.5,
3253
  gp = grid::gpar(fontface = "bold", cex = 1.8, col = "#006F71"))
3254
+
3255
+ # Add team logo on the right if available (mirror)
3256
+ if (!is.null(logo_path) && file.exists(logo_path)) {
3257
+ add_team_logo(logo_path, x = 0.92, y = 0.5, width = 0.06, height = grid::unit(0.8, "npc"))
3258
+ }
3259
+
3260
  grid::popViewport()
3261
 
3262
  # Summary section
3263
+ grid::grid.text("Summary", x = 0.5, y = 0.92,
3264
  gp = grid::gpar(fontface = "bold", cex = 1.1, col = "#006F71"))
3265
 
3266
  summary_headers <- names(summary_stats)
 
3269
 
3270
  x_start <- 0.5 - sum(summary_widths)/2
3271
  x_pos <- c(x_start, x_start + cumsum(summary_widths[-length(summary_widths)]))
3272
+ y_top <- 0.905
3273
  row_h <- 0.020
3274
 
3275
  for (i in seq_along(summary_headers)) {
 
3287
  gp = grid::gpar(cex = 0.62))
3288
  }
3289
 
3290
+ # Row 1: Movement plot (left) | Velocity distribution (right)
3291
  grid::pushViewport(grid::viewport(x = 0.25, y = charts_y_top, width = 0.45, height = charts_height, just = c("center","top")))
3292
  tryCatch(print(movement_plot, newpage = FALSE), error = function(e) NULL)
3293
  grid::popViewport()
 
3296
  tryCatch(print(velo_plot, newpage = FALSE), error = function(e) NULL)
3297
  grid::popViewport()
3298
 
3299
+ # Row 2: Count plot (left) | ENLARGED Release side plot (right)
3300
+ # Count plot on left
3301
+ grid::pushViewport(grid::viewport(x = 0.27, y = count_y_top, width = 0.50, height = count_height, just = c("center","top")))
3302
  tryCatch(print(count_plot, newpage = FALSE), error = function(e) NULL)
3303
  grid::popViewport()
3304
 
3305
+ # ENLARGED Release side plot on right - increased width and height
3306
+ grid::pushViewport(grid::viewport(x = 0.77, y = count_y_top, width = 0.44, height = count_height, just = c("center","top")))
3307
  tryCatch(print(relside_height_plot, newpage = FALSE), error = function(e) NULL)
3308
  grid::popViewport()
 
3309
 
3310
  # Pitch Characteristics table
3311
  grid::grid.text("Pitch Characteristics", x = 0.5, y = y_top_char + 0.015,
 
3314
  char_headers <- names(pitch_char)
3315
  num_char_cols <- length(char_headers)
3316
 
 
3317
  if (num_char_cols > 1) {
3318
  char_widths <- c(0.10, rep((1 - 0.10 - 0.06) / (num_char_cols - 1), num_char_cols - 1))
3319
  } else {
 
3336
  gp = grid::gpar(col = "white", cex = 0.50, fontface = "bold"))
3337
  }
3338
 
 
3339
  i_col_pitch <- match("Pitch", char_headers)
3340
  has_pitchcol <- !is.na(i_col_pitch) && i_col_pitch >= 1
3341
 
 
3350
 
3351
  colname <- char_headers[i]
3352
 
 
3353
  bg <- "#FFFFFF"
3354
  if (r >= 1 && r <= nrow(pitch_colors_matrix) && i >= 1 && i <= ncol(pitch_colors_matrix)) {
3355
  bg <- pitch_colors_matrix[r, i]
3356
  if (is.na(bg) || !nzchar(bg)) bg <- "#FFFFFF"
3357
  }
3358
 
 
3359
  if (has_pitchcol && identical(colname, "Pitch") && !is.na(pitch_name) && pitch_name %in% names(pitch_colors)) {
3360
  bg <- pitch_colors[[pitch_name]]
3361
  }
 
3363
  grid::grid.rect(x = x_pos_char[i], y = y_row, width = char_widths[i]*0.985, height = row_h_char,
3364
  just = c("left","top"), gp = grid::gpar(fill = bg, col = "grey80", lwd = 0.3))
3365
 
 
3366
  val <- get_cell_value(pitch_char, colname, r)
3367
 
3368
  display_val <- if (is.numeric(val)) {