TimStats commited on
Commit
9faa99d
·
verified ·
1 Parent(s): 40ad80b

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +204 -209
app.R CHANGED
@@ -1,3 +1,4 @@
 
1
  library(shiny)
2
  library(plotly)
3
  library(gridlayout)
@@ -18,9 +19,27 @@ library(grid)
18
  library(gridExtra)
19
  library(png)
20
  library(xgboost)
 
 
 
21
  pdf(file = NULL)
22
  Sys.setenv(TZ='EST')
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  is_barrel <- function(df) {
25
  df$barrel <- with(df, ifelse(hit_angle <= 50 & hit_speed >= 97 & hit_speed * 1.5 -
26
  hit_angle >= 117 & hit_speed + hit_angle >= 123, 1, 0))
@@ -28,7 +47,7 @@ is_barrel <- function(df) {
28
  }
29
 
30
  VAA <- function(milbtotal){
31
- milbtotal <- milbtotal |>
32
  mutate(VAA = -atan((vz0+(az*(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))-vy0)/
33
  ay))/(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))))*(180/pi))
34
  }
@@ -37,9 +56,9 @@ pitcher_summary <- function(game_pk,date){
37
  gdate <- as.Date.character(date)
38
  gdate <- as.Date(gdate)
39
  tmilb <- mlb_pbp(game_pk)
40
- tmilb <- tmilb |>
41
  filter(type == "pitch")
42
- tmilb <- tmilb |>
43
  select(matchup.batter.fullName,matchup.batter.id,matchup.pitcher.fullName,
44
  matchup.pitcher.id,result.event,details.description,details.type.description,
45
  result.description,pitchData.startSpeed,pitchData.plateTime,pitchData.zone,
@@ -58,10 +77,10 @@ pitcher_summary <- function(game_pk,date){
58
  "IVB","HB","hit_speed","hit_angle","hit_distance","inPlay",
59
  "lastPitch","spinDirection","phand")
60
  tmilb <- is_barrel(tmilb)
61
- tmilb <- tmilb |>
62
  mutate(is_strike_swinging = ifelse(description == "Swinging Strike" |
63
  description == "Foul Tip",TRUE,FALSE))
64
- tmilb <- tmilb |>
65
  mutate(date = gdate)
66
  return(tmilb)
67
  }
@@ -104,69 +123,46 @@ pitch_plot <- function(game){
104
  axis.ticks.y=element_blank())
105
  }
106
 
107
- # Assume FB.model, Off.model, and Break.model are already loaded in the environment
 
 
 
108
 
109
- # Function to calculate VAA (Vertical Approach Angle)
110
- calculate_VAA <- function(vz0, ay, vy0, y0) {
111
- -atan((vz0 + (ay * (-sqrt((vy0*vy0) - (2*ay*(y0-(17/12)))) - vy0) / ay)) /
112
- (-sqrt((vy0*vy0) - (2*ay*(y0-(17/12)))))) * (180/pi)
113
  }
114
 
115
- # Function to calculate EAA (Effective Approach Angle)
116
  calculate_EAA <- function(extension) {
117
  extension / 6.3
118
  }
119
 
120
- # Function to calculate SADiff (Spin Axis Differential)
121
  calculate_SADiff <- function(pfxX, pfxZ, spinDirection) {
122
- # Calculate initial inSA
123
  inSA <- atan2(pfxZ, pfxX) * 180/pi + 90
124
-
125
- # Adjust inSA if it's negative
126
  inSA <- ifelse(inSA < 0, inSA + 360, inSA)
127
-
128
- # Calculate SADiff
129
  SADiff <- spinDirection - inSA
130
-
131
- # Adjust SADiff to be within -180 to 180 range
132
  SADiff <- ifelse(SADiff > 180, SADiff - 360, SADiff)
133
  SADiff <- ifelse(SADiff < -180, SADiff + 360, SADiff)
134
-
135
  return(SADiff)
136
  }
137
 
138
- # Function to scale TimStuff
139
  scale_TimStuff <- function(raw_score, model_mean, model_sd) {
140
  scaled_score <- (raw_score - model_mean) / model_sd
141
  result <- 50 - (scaled_score * 10)
142
-
143
- # Add logging
144
- # cat("Raw score:", raw_score, "\n")
145
- # cat("Model mean:", model_mean, "\n")
146
- # cat("Model SD:", model_sd, "\n")
147
- # cat("Scaled score:", scaled_score, "\n")
148
- # cat("Final result:", result, "\n")
149
-
150
- # Ensure the result is within a reasonable range
151
- # result <- max(min(result, 100), 0)
152
-
153
  return(result)
154
  }
155
- FB <- xgb.load('FB.model')
156
- Off <- xgb.load('Off.model')
157
- Break <- xgb.load('Break.model')
158
- # Modify the summary_table function
159
  summary_table <- function(game) {
160
  rows <- nrow(game)
161
- sumtable <- game |>
162
  mutate(
163
- VAA = calculate_VAA(vz0, ay, vy0, y0),
164
  EAA = calculate_EAA(extension),
165
  SADiff = calculate_SADiff(pfxX, pfxZ,spinDirection)
166
- ) |>
167
  mutate(team_fielding_id = ifelse(description == "Called Strike" |
168
  description == "Swinging Strike" |
169
- description == "Swinging Strike (Blocked)", 1, 0)) |>
170
  mutate(swing = ifelse(description == "Foul" |
171
  description == "Foul Pitchout" |
172
  description == "In play, no out" |
@@ -174,10 +170,10 @@ summary_table <- function(game) {
174
  description == "In play, run(s)" |
175
  description == "Swinging Strike" |
176
  description == "swinging Strike (Blocked)" |
177
- description == "Foul Tip", 1, 0)) |>
178
- mutate(is_strike_swinging = ifelse(is_strike_swinging == TRUE, 1, 0)) |>
179
- mutate(Pitch = pitch_name) |>
180
- rowwise() |>
181
  mutate(TimStuff = if (phand == 'L') {
182
  case_when(
183
  Pitch %in% c("Four-Seam Fastball", "Sinker", "Cutter", "Fastball") ~
@@ -216,8 +212,8 @@ summary_table <- function(game) {
216
  -0.004822031, 0.007912765)),
217
  TRUE ~ NA_real_
218
  )
219
- }) |>
220
- group_by(Pitch) |>
221
  summarize(
222
  Pitches = n(),
223
  'Pitch%' = round(sum(Pitches)/sum(rows) * 100, digits = 1),
@@ -227,39 +223,34 @@ summary_table <- function(game) {
227
  'IVB' = round(mean(IVB, na.rm = TRUE), digits = 1),
228
  'HB' = round(mean(HB, na.rm = TRUE), digits = 1),
229
  'VAA' = round(mean(VAA, na.rm = TRUE), digits = 1),
230
- # 'EAA' = round(mean(EAA, na.rm = TRUE), digits = 2),
231
- # 'x0' = round(mean(x0, na.rm = TRUE), digits = 2),
232
- # 'z0' = round(mean(z0, na.rm = TRUE), digits = 2),
233
- # 'SADiff' = round(mean(SADiff, na.rm = TRUE), digits = 2),
234
  'CSW%' = round(sum(team_fielding_id, na.rm = TRUE) / sum(!is.na(team_fielding_id)) * 100, digits = 1),
235
  'Whiff%' = round(sum(is_strike_swinging, na.rm = TRUE) / sum(swing, na.rm = TRUE) * 100, digits = 1),
236
  'TimStuff' = round(mean(TimStuff, na.rm = TRUE), digits = 0)
237
- ) |>
238
  arrange(-Pitches)
239
 
240
  return(sumtable)
241
  }
242
 
243
- # The rest of your code remains the same
244
-
245
  mlbid <- mlb_schedule(season = 2024, level_ids = "1")
246
- mlbteamH <- mlbid |>
247
  select(teams_home_team_name)
248
  mlbteamH <- distinct(mlbteamH)
249
- mlbteamA <- mlbid |>
250
  select(teams_away_team_name)
251
  mlbteamA <- distinct(mlbteamA)
252
 
253
  aaaid <- mlb_schedule(season = 2024, level_ids = "11")
254
- aaateamH <- aaaid |>
255
  select(teams_home_team_name)
256
  aaateamH <- distinct(aaateamH)
257
- aaateamA <- aaaid |>
258
  select(teams_away_team_name)
259
  aaateamA <- distinct(aaateamA)
260
 
261
  fslid <- mlb_schedule(season = 2024, level_ids = "14")
262
- fslid <- fslid |>
263
  filter(teams_home_team_name == "Daytona Tortugas" |
264
  teams_home_team_name == "Jupiter Hammerheads" |
265
  teams_home_team_name == "Palm Beach Cardinals" |
@@ -270,21 +261,22 @@ fslid <- fslid |>
270
  teams_home_team_name == "Fort Myers Mighty Mussels" |
271
  teams_home_team_name == "Lakeland Flying Tigers" |
272
  teams_home_team_name == "Tampa Tarpons")
273
- fslteamH <- fslid |>
274
  select(teams_home_team_name)
275
  fslteamH <- distinct(fslteamH)
276
- fslteamA <- fslid |>
277
  select(teams_away_team_name)
278
  fslteamA <- distinct(fslteamA)
279
 
280
  sbid <- mlb_schedule(season = 2024, level_ids = "22")
281
- sbteamH <- sbid |>
282
  select(teams_home_team_name)
283
  sbteamH <- distinct(sbteamH)
284
- sbteamA <- sbid |>
285
  select(teams_away_team_name)
286
  sbteamA <- distinct(sbteamA)
287
 
 
288
  ui <- fluidPage(
289
  tags$head(
290
  tags$style(HTML("
@@ -309,7 +301,6 @@ ui <- fluidPage(
309
  actionButton("update", "Find Pitcher", icon("magnifying-glass"),
310
  style = "color: #FFFFFF; background-color: #0077B6; width: 100%;"),
311
  selectizeInput("pitcher", "Pitcher Name:", c(" ")),
312
- #selectizeInput("league", "MLB or MiLB Photo", c("MLB", "MiLB")),
313
  textInput("title", "Card Title"),
314
  actionButton("update1", "Make Card", icon("plus"),
315
  style = "color: #FFFFFF; background-color: #0077B6; width: 100%;"),
@@ -320,7 +311,6 @@ ui <- fluidPage(
320
  )
321
  )
322
  )
323
-
324
  server <- function(input, output, session) {
325
  observeEvent(input$level, {
326
  if(input$level == "AAA"){
@@ -342,156 +332,162 @@ server <- function(input, output, session) {
342
  })
343
 
344
  observeEvent(input$update, {
345
- if(input$level == "AAA"){
346
- pname <- aaaid |>
347
- filter(date == input$date) |>
348
- filter(teams_home_team_name == input$homeT) |>
349
- filter(teams_away_team_name == input$awayT) |>
350
- filter(game_number == input$gamenum)
351
- pname <- tryCatch({pitcher_summary(pname[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
352
- updateSelectizeInput(session = getDefaultReactiveDomain(),"pitcher","Pitcher:",choices = pname[,3])
353
- }
354
- if(input$level == "FSL"){
355
- pname <- fslid |>
356
- filter(date == input$date) |>
357
- filter(teams_home_team_name == input$homeT) |>
358
- filter(teams_away_team_name == input$awayT) |>
359
- filter(game_number == input$gamenum)
360
- pname <- tryCatch({pitcher_summary(pname[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
361
- updateSelectizeInput(session = getDefaultReactiveDomain(),"pitcher","Pitcher:",choices = pname[,3])
362
- }
363
- if(input$level == "MLB"){
364
- pname <- mlbid |>
365
- filter(date == as.character.Date(input$date)) |>
366
- filter(teams_home_team_name == input$homeT) |>
367
- filter(teams_away_team_name == input$awayT) |>
368
- filter(game_number == input$gamenum)
369
- pname <- tryCatch({pitcher_summary(pname[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
370
- updateSelectizeInput(session = getDefaultReactiveDomain(),"pitcher","Pitcher:",choices = pname[,3])
371
- }
372
- if(input$level == "College (Statcast Parks Only)"){
373
- pname <- sbid |>
374
- filter(date == input$date) |>
375
- filter(teams_home_team_name == input$homeT) |>
376
- filter(teams_away_team_name == input$awayT) |>
377
- filter(game_number == input$gamenum)
378
- pname <- tryCatch({pitcher_summary(pname[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
379
- updateSelectizeInput(session = getDefaultReactiveDomain(),"pitcher","Pitcher:",choices = pname[,3])
380
- }
 
 
 
 
 
 
381
  })
382
 
383
  combinedPlot <- reactiveVal()
384
  observeEvent(input$update1, {
385
- if(input$level == "MLB"){
386
- id <- mlbid |>
387
- filter(date == as.character.Date(input$date)) |>
388
- filter(teams_home_team_name == input$homeT) |>
389
- filter(teams_away_team_name == input$awayT) |>
390
- filter(game_number == input$gamenum)
391
- game <- tryCatch({pitcher_summary(id[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
392
- game <- game |>
393
- filter(`Pitcher Name` == input$pitcher)
394
- id <- as.character(game[1,4])
395
- }
396
- if(input$level == "FSL"){
397
- id <- fslid |>
398
- filter(date == as.character.Date(input$date)) |>
399
- filter(teams_home_team_name == input$homeT) |>
400
- filter(teams_away_team_name == input$awayT) |>
401
- filter(game_number == input$gamenum)
402
- game <- tryCatch({pitcher_summary(id[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
403
- game <- game |>
404
- filter(`Pitcher Name` == input$pitcher)
405
- id <- as.character(game[1,4])
406
- }
407
- if(input$level == "AAA"){
408
- id <- aaaid |>
409
- filter(date == as.character.Date(input$date)) |>
410
- filter(teams_home_team_name == input$homeT) |>
411
- filter(teams_away_team_name == input$awayT) |>
412
- filter(game_number == input$gamenum)
413
- game <- tryCatch({pitcher_summary(id[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
414
- game <- game |>
415
- filter(`Pitcher Name` == input$pitcher)
416
- id <- as.character(game[1,4])
417
- }
418
- if(input$level == "College (Statcast Parks Only)"){
419
- id <- sbid |>
420
- filter(date == as.character.Date(input$date)) |>
421
- filter(teams_home_team_name == input$homeT) |>
422
- filter(teams_away_team_name == input$awayT) |>
423
- filter(game_number == input$gamenum)
424
- game <- tryCatch({pitcher_summary(id[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
425
- game <- game |>
426
- filter(`Pitcher Name` == input$pitcher)
427
- id <- as.character(game[1,4])
428
- }
429
-
430
- break_plot <- break_plot(game)
431
- pitch_plot <- pitch_plot(game)
432
-
433
- # Create table plot
434
- # Update the tableGrob in the observeEvent(input$update1, {...}) block
435
- table_plot <- tableGrob(summary_table(game), theme = ttheme_default(
436
- core = list(fg_params = list(cex = 1.7)),
437
- colhead = list(fg_params = list(cex = 1.2)),
438
- rowhead = list(fg_params = list(cex = 2.5))))
439
- table_plot$widths[[1]] <- unit(0, "cm")
440
-
441
- # Find the text grobs in the first column
442
- text_grobs <- which(sapply(table_plot$grobs, function(g) {
443
- g$name == "core-fg-1" && inherits(g$children[[1]], "text")
444
- }))
445
-
446
- # Remove the numbers from these text grobs
447
- for (i in text_grobs) {
448
- text <- table_plot$grobs[[i]]$children[[1]]$label
449
- table_plot$grobs[[i]]$children[[1]]$label <- sub("^\\d+\\s*", "", text)
450
- }
451
-
452
- # Draw the modified table
453
- grid.newpage()
454
- grid.draw(table_plot)
455
- # Download and process image
456
- # if(input$league == "MLB"){
457
- # y <- paste("https://midfield.mlbstatic.com/v1/people/","/mlb/300?circle=false",sep = id)
458
- # } else {
459
- # y <- paste("https://midfield.mlbstatic.com/v1/people/","/milb/300?circle=false",sep = id)
460
- # }
461
  tryCatch({
462
- # Try MLB URL first
463
- y <- paste0("https://midfield.mlbstatic.com/v1/people/", id, "/mlb/300?circle=false")
464
- # Attempt to use the URL (replace this with your actual usage code)
465
- # For example: response <- httr::GET(y)
466
- # If it succeeds, it will use this URL
467
- }, error = function(e) {
468
- # If MLB URL fails, try MiLB URL
469
- tryCatch({
470
- y <- paste0("https://midfield.mlbstatic.com/v1/people/", id, "/milb/300?circle=false")
471
- # Attempt to use the MiLB URL
472
- # For example: response <- httr::GET(y)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
  }, error = function(e) {
474
- # If both fail, you can handle it here
475
- stop("Both MLB and MiLB URLs failed")
476
  })
477
- })
478
- photo <- tempfile()
479
- download.file(y, photo, mode = 'wb')
480
- img <- readPNG(photo)
481
- img_grob <- rasterGrob(img, interpolate = TRUE)
482
-
483
- # Combine all elements into one plot
484
- combined <- grid.arrange(
485
- arrangeGrob(table_plot, img_grob, ncol = 2, widths = c(2/3, 1/3)),
486
- arrangeGrob(break_plot, pitch_plot, ncol = 2),
487
- nrow = 2,
488
- top = textGrob(paste(input$title, "Made by @TimStats", sep = " "), gp = gpar(fontsize = 20, font = 2))
489
- )
490
-
491
- combinedPlot(combined)
492
-
493
- output$combinedPlot <- renderPlot({
494
- grid.draw(combinedPlot())
 
 
 
 
 
 
 
 
 
 
 
 
495
  })
496
  })
497
 
@@ -500,10 +496,9 @@ server <- function(input, output, session) {
500
  paste("baseball_card_", Sys.Date(), ".png", sep = "")
501
  },
502
  content = function(file) {
503
- ggsave(file, plot = combinedPlot(), width = 19, height = 12, dpi = 300)
504
  }
505
  )
506
  }
507
 
508
-
509
  shinyApp(ui, server)
 
1
+ # Load required libraries
2
  library(shiny)
3
  library(plotly)
4
  library(gridlayout)
 
19
  library(gridExtra)
20
  library(png)
21
  library(xgboost)
22
+ library(httr)
23
+ #library(magick)
24
+
25
  pdf(file = NULL)
26
  Sys.setenv(TZ='EST')
27
 
28
+ # Helper functions
29
+ download_and_process_image <- function(url) {
30
+ tryCatch({
31
+ temp_file <- tempfile(fileext = ".jpg")
32
+ download.file(url, temp_file, mode = "wb")
33
+ img <- image_read(temp_file)
34
+ png_file <- tempfile(fileext = ".png")
35
+ image_write(img, path = png_file, format = "png")
36
+ return(png_file)
37
+ }, error = function(e) {
38
+ warning(paste("Error processing image:", e$message))
39
+ return(NULL)
40
+ })
41
+ }
42
+
43
  is_barrel <- function(df) {
44
  df$barrel <- with(df, ifelse(hit_angle <= 50 & hit_speed >= 97 & hit_speed * 1.5 -
45
  hit_angle >= 117 & hit_speed + hit_angle >= 123, 1, 0))
 
47
  }
48
 
49
  VAA <- function(milbtotal){
50
+ milbtotal <- milbtotal %>%
51
  mutate(VAA = -atan((vz0+(az*(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))-vy0)/
52
  ay))/(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))))*(180/pi))
53
  }
 
56
  gdate <- as.Date.character(date)
57
  gdate <- as.Date(gdate)
58
  tmilb <- mlb_pbp(game_pk)
59
+ tmilb <- tmilb %>%
60
  filter(type == "pitch")
61
+ tmilb <- tmilb %>%
62
  select(matchup.batter.fullName,matchup.batter.id,matchup.pitcher.fullName,
63
  matchup.pitcher.id,result.event,details.description,details.type.description,
64
  result.description,pitchData.startSpeed,pitchData.plateTime,pitchData.zone,
 
77
  "IVB","HB","hit_speed","hit_angle","hit_distance","inPlay",
78
  "lastPitch","spinDirection","phand")
79
  tmilb <- is_barrel(tmilb)
80
+ tmilb <- tmilb %>%
81
  mutate(is_strike_swinging = ifelse(description == "Swinging Strike" |
82
  description == "Foul Tip",TRUE,FALSE))
83
+ tmilb <- tmilb %>%
84
  mutate(date = gdate)
85
  return(tmilb)
86
  }
 
123
  axis.ticks.y=element_blank())
124
  }
125
 
126
+ # Load models
127
+ FB <- xgb.load('FB.model')
128
+ Off <- xgb.load('Off.model')
129
+ Break <- xgb.load('Break.model')
130
 
131
+ calculate_VAA <- function(vz0, ay, az, vy0, y0) {
132
+ -atan((vz0+(az*(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))-vy0)/
133
+ ay))/(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))))*(180/pi)
 
134
  }
135
 
 
136
  calculate_EAA <- function(extension) {
137
  extension / 6.3
138
  }
139
 
 
140
  calculate_SADiff <- function(pfxX, pfxZ, spinDirection) {
 
141
  inSA <- atan2(pfxZ, pfxX) * 180/pi + 90
 
 
142
  inSA <- ifelse(inSA < 0, inSA + 360, inSA)
 
 
143
  SADiff <- spinDirection - inSA
 
 
144
  SADiff <- ifelse(SADiff > 180, SADiff - 360, SADiff)
145
  SADiff <- ifelse(SADiff < -180, SADiff + 360, SADiff)
 
146
  return(SADiff)
147
  }
148
 
 
149
  scale_TimStuff <- function(raw_score, model_mean, model_sd) {
150
  scaled_score <- (raw_score - model_mean) / model_sd
151
  result <- 50 - (scaled_score * 10)
 
 
 
 
 
 
 
 
 
 
 
152
  return(result)
153
  }
154
+
 
 
 
155
  summary_table <- function(game) {
156
  rows <- nrow(game)
157
+ sumtable <- game %>%
158
  mutate(
159
+ VAA = calculate_VAA(vz0, ay,az, vy0, y0),
160
  EAA = calculate_EAA(extension),
161
  SADiff = calculate_SADiff(pfxX, pfxZ,spinDirection)
162
+ ) %>%
163
  mutate(team_fielding_id = ifelse(description == "Called Strike" |
164
  description == "Swinging Strike" |
165
+ description == "Swinging Strike (Blocked)", 1, 0)) %>%
166
  mutate(swing = ifelse(description == "Foul" |
167
  description == "Foul Pitchout" |
168
  description == "In play, no out" |
 
170
  description == "In play, run(s)" |
171
  description == "Swinging Strike" |
172
  description == "swinging Strike (Blocked)" |
173
+ description == "Foul Tip", 1, 0)) %>%
174
+ mutate(is_strike_swinging = ifelse(is_strike_swinging == TRUE, 1, 0)) %>%
175
+ mutate(Pitch = pitch_name) %>%
176
+ rowwise() %>%
177
  mutate(TimStuff = if (phand == 'L') {
178
  case_when(
179
  Pitch %in% c("Four-Seam Fastball", "Sinker", "Cutter", "Fastball") ~
 
212
  -0.004822031, 0.007912765)),
213
  TRUE ~ NA_real_
214
  )
215
+ }) %>%
216
+ group_by(Pitch) %>%
217
  summarize(
218
  Pitches = n(),
219
  'Pitch%' = round(sum(Pitches)/sum(rows) * 100, digits = 1),
 
223
  'IVB' = round(mean(IVB, na.rm = TRUE), digits = 1),
224
  'HB' = round(mean(HB, na.rm = TRUE), digits = 1),
225
  'VAA' = round(mean(VAA, na.rm = TRUE), digits = 1),
 
 
 
 
226
  'CSW%' = round(sum(team_fielding_id, na.rm = TRUE) / sum(!is.na(team_fielding_id)) * 100, digits = 1),
227
  'Whiff%' = round(sum(is_strike_swinging, na.rm = TRUE) / sum(swing, na.rm = TRUE) * 100, digits = 1),
228
  'TimStuff' = round(mean(TimStuff, na.rm = TRUE), digits = 0)
229
+ ) %>%
230
  arrange(-Pitches)
231
 
232
  return(sumtable)
233
  }
234
 
235
+ # Initialize schedule data
 
236
  mlbid <- mlb_schedule(season = 2024, level_ids = "1")
237
+ mlbteamH <- mlbid %>%
238
  select(teams_home_team_name)
239
  mlbteamH <- distinct(mlbteamH)
240
+ mlbteamA <- mlbid %>%
241
  select(teams_away_team_name)
242
  mlbteamA <- distinct(mlbteamA)
243
 
244
  aaaid <- mlb_schedule(season = 2024, level_ids = "11")
245
+ aaateamH <- aaaid %>%
246
  select(teams_home_team_name)
247
  aaateamH <- distinct(aaateamH)
248
+ aaateamA <- aaaid %>%
249
  select(teams_away_team_name)
250
  aaateamA <- distinct(aaateamA)
251
 
252
  fslid <- mlb_schedule(season = 2024, level_ids = "14")
253
+ fslid <- fslid %>%
254
  filter(teams_home_team_name == "Daytona Tortugas" |
255
  teams_home_team_name == "Jupiter Hammerheads" |
256
  teams_home_team_name == "Palm Beach Cardinals" |
 
261
  teams_home_team_name == "Fort Myers Mighty Mussels" |
262
  teams_home_team_name == "Lakeland Flying Tigers" |
263
  teams_home_team_name == "Tampa Tarpons")
264
+ fslteamH <- fslid %>%
265
  select(teams_home_team_name)
266
  fslteamH <- distinct(fslteamH)
267
+ fslteamA <- fslid %>%
268
  select(teams_away_team_name)
269
  fslteamA <- distinct(fslteamA)
270
 
271
  sbid <- mlb_schedule(season = 2024, level_ids = "22")
272
+ sbteamH <- sbid %>%
273
  select(teams_home_team_name)
274
  sbteamH <- distinct(sbteamH)
275
+ sbteamA <- sbid %>%
276
  select(teams_away_team_name)
277
  sbteamA <- distinct(sbteamA)
278
 
279
+ # UI Definition
280
  ui <- fluidPage(
281
  tags$head(
282
  tags$style(HTML("
 
301
  actionButton("update", "Find Pitcher", icon("magnifying-glass"),
302
  style = "color: #FFFFFF; background-color: #0077B6; width: 100%;"),
303
  selectizeInput("pitcher", "Pitcher Name:", c(" ")),
 
304
  textInput("title", "Card Title"),
305
  actionButton("update1", "Make Card", icon("plus"),
306
  style = "color: #FFFFFF; background-color: #0077B6; width: 100%;"),
 
311
  )
312
  )
313
  )
 
314
  server <- function(input, output, session) {
315
  observeEvent(input$level, {
316
  if(input$level == "AAA"){
 
332
  })
333
 
334
  observeEvent(input$update, {
335
+ tryCatch({
336
+ if(input$level == "AAA"){
337
+ pname <- aaaid %>%
338
+ filter(date == input$date) %>%
339
+ filter(teams_home_team_name == input$homeT) %>%
340
+ filter(teams_away_team_name == input$awayT) %>%
341
+ filter(game_number == input$gamenum)
342
+ pname <- pitcher_summary(pname[,6],input$date)
343
+ }
344
+ if(input$level == "FSL"){
345
+ pname <- fslid %>%
346
+ filter(date == input$date) %>%
347
+ filter(teams_home_team_name == input$homeT) %>%
348
+ filter(teams_away_team_name == input$awayT) %>%
349
+ filter(game_number == input$gamenum)
350
+ pname <- pitcher_summary(pname[,6],input$date)
351
+ }
352
+ if(input$level == "MLB"){
353
+ pname <- mlbid %>%
354
+ filter(date == as.character.Date(input$date)) %>%
355
+ filter(teams_home_team_name == input$homeT) %>%
356
+ filter(teams_away_team_name == input$awayT) %>%
357
+ filter(game_number == input$gamenum)
358
+ pname <- pitcher_summary(pname[,6],input$date)
359
+ }
360
+ if(input$level == "College (Statcast Parks Only)"){
361
+ pname <- sbid %>%
362
+ filter(date == input$date) %>%
363
+ filter(teams_home_team_name == input$homeT) %>%
364
+ filter(teams_away_team_name == input$awayT) %>%
365
+ filter(game_number == input$gamenum)
366
+ pname <- pitcher_summary(pname[,6],input$date)
367
+ }
368
+
369
+ if(nrow(pname) == 0) {
370
+ showNotification("No pitchers found for the selected game.", type = "warning")
371
+ } else {
372
+ updateSelectizeInput(session = getDefaultReactiveDomain(),"pitcher","Pitcher:",choices = pname[,3])
373
+ }
374
+ }, error = function(e) {
375
+ showNotification(paste("Error finding pitchers:", e$message), type = "error")
376
+ })
377
  })
378
 
379
  combinedPlot <- reactiveVal()
380
  observeEvent(input$update1, {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  tryCatch({
382
+ if(input$level == "MLB"){
383
+ id <- mlbid %>%
384
+ filter(date == as.character.Date(input$date)) %>%
385
+ filter(teams_home_team_name == input$homeT) %>%
386
+ filter(teams_away_team_name == input$awayT) %>%
387
+ filter(game_number == input$gamenum)
388
+ game <- pitcher_summary(id[,6],input$date)
389
+ game <- game %>%
390
+ filter(`Pitcher Name` == input$pitcher)
391
+ id <- as.character(game[1,4])
392
+ }
393
+ if(input$level == "FSL"){
394
+ id <- fslid %>%
395
+ filter(date == as.character.Date(input$date)) %>%
396
+ filter(teams_home_team_name == input$homeT) %>%
397
+ filter(teams_away_team_name == input$awayT) %>%
398
+ filter(game_number == input$gamenum)
399
+ game <- pitcher_summary(id[,6],input$date)
400
+ game <- game %>%
401
+ filter(`Pitcher Name` == input$pitcher)
402
+ id <- as.character(game[1,4])
403
+ }
404
+ if(input$level == "AAA"){
405
+ id <- aaaid %>%
406
+ filter(date == as.character.Date(input$date)) %>%
407
+ filter(teams_home_team_name == input$homeT) %>%
408
+ filter(teams_away_team_name == input$awayT) %>%
409
+ filter(game_number == input$gamenum)
410
+ game <- pitcher_summary(id[,6],input$date)
411
+ game <- game %>%
412
+ filter(`Pitcher Name` == input$pitcher)
413
+ id <- as.character(game[1,4])
414
+ }
415
+ if(input$level == "College (Statcast Parks Only)"){
416
+ id <- sbid %>%
417
+ filter(date == as.character.Date(input$date)) %>%
418
+ filter(teams_home_team_name == input$homeT) %>%
419
+ filter(teams_away_team_name == input$awayT) %>%
420
+ filter(game_number == input$gamenum)
421
+ game <- pitcher_summary(id[,6],input$date)
422
+ game <- game %>%
423
+ filter(`Pitcher Name` == input$pitcher)
424
+ id <- as.character(game[1,4])
425
+ }
426
+
427
+ if(nrow(game) == 0) {
428
+ showNotification("No data available for the selected pitcher.", type = "warning")
429
+ return()
430
+ }
431
+
432
+ break_plot <- break_plot(game)
433
+ pitch_plot <- pitch_plot(game)
434
+
435
+ table_plot <- tableGrob(summary_table(game), theme = ttheme_default(
436
+ core = list(fg_params = list(cex = 1.7)),
437
+ colhead = list(fg_params = list(cex = 1.2)),
438
+ rowhead = list(fg_params = list(cex = 2.5))))
439
+ table_plot$widths[[1]] <- unit(0, "cm")
440
+
441
+ text_grobs <- which(sapply(table_plot$grobs, function(g) {
442
+ g$name == "core-fg-1" && inherits(g$children[[1]], "text")
443
+ }))
444
+
445
+ for (i in text_grobs) {
446
+ text <- table_plot$grobs[[i]]$children[[1]]$label
447
+ table_plot$grobs[[i]]$children[[1]]$label <- sub("^\\d+\\s*", "", text)
448
+ }
449
+
450
+ grid.newpage()
451
+ grid.draw(table_plot)
452
+
453
+ mlb_url <- paste0("https://midfield.mlbstatic.com/v1/people/", id, "/mlb/300?circle=false")
454
+ milb_url <- paste0("https://midfield.mlbstatic.com/v1/people/", id, "/milb/300?circle=false")
455
+
456
+ img_file <- tryCatch({
457
+ download_and_process_image(mlb_url)
458
  }, error = function(e) {
459
+ NULL
 
460
  })
461
+
462
+ if (is.null(img_file)) {
463
+ img_file <- tryCatch({
464
+ download_and_process_image(milb_url)
465
+ }, error = function(e) {
466
+ NULL
467
+ })
468
+ }
469
+
470
+ if (!is.null(img_file)) {
471
+ img <- readPNG(img_file)
472
+ img_grob <- rasterGrob(img, interpolate = TRUE)
473
+ } else {
474
+ img_grob <- textGrob("Image not available", gp = gpar(col = "red", fontsize = 20))
475
+ }
476
+
477
+ combined <- grid.arrange(
478
+ arrangeGrob(table_plot, img_grob, ncol = 2, widths = c(2/3, 1/3)),
479
+ arrangeGrob(break_plot, pitch_plot, ncol = 2),
480
+ nrow = 2,
481
+ top = textGrob(paste(input$title, "Made by @TimStats", sep = " "), gp = gpar(fontsize = 20, font = 2))
482
+ )
483
+
484
+ combinedPlot(combined)
485
+
486
+ output$combinedPlot <- renderPlot({
487
+ grid.draw(combinedPlot())
488
+ })
489
+ }, error = function(e) {
490
+ showNotification(paste("Error generating card:", e$message), type = "error")
491
  })
492
  })
493
 
 
496
  paste("baseball_card_", Sys.Date(), ".png", sep = "")
497
  },
498
  content = function(file) {
499
+ ggsave(file, plot = combinedPlot(), width = 18, height = 12, dpi = 300)
500
  }
501
  )
502
  }
503
 
 
504
  shinyApp(ui, server)