library(shiny) library(tidyverse) data1 <- read.csv("data20-24.csv", header = TRUE, check.names = FALSE, fileEncoding = "UTF-8") AAaveragePT <- data1 %>% mutate(arm_angle = round(arm_angle, digits = 0)) %>% group_by(arm_angle, phand, pitch_name) %>% summarise( AvgIVB = mean(IVB, na.rm = TRUE), AvgHB = mean(HB, na.rm = TRUE), .groups = 'drop' ) break_plot_Szn <- function(game,data1,sdate,edate) { game <- game %>% left_join(pitch_type_lookup, by = c("pitch_name")) title <- paste0(unique(game$`Pitcher Name`)," ",sdate," to ",edate) # Get pitcher's handedness pitcher_hand <- unique(game$phand) angle_degrees <- round(mean(game$arm_angle,na.rm = TRUE),digits = 1) # Calculate average movement by arm angle and pitch type from season data # AAaveragePT <- data1 %>% # mutate(arm_angle = round(arm_angle, digits = 0)) %>% # group_by(arm_angle, phand, pitch_name) %>% # summarise( # AvgIVB = mean(IVB, na.rm = TRUE), # AvgHB = mean(HB, na.rm = TRUE), # .groups = 'drop' # ) pitch_colors <- c( "FF" = "#FF4136", "SI" = "#FF851B", "FC" = "#FFDC00", "CH" = "#2ECC40", "SL" = "#0074D9", "ST" = "#ED68ED", "CU" = "#B10DC9", "FS" = "#01FF70", "KC" = "#85144b", "SV" = "#3D9970", "KN" = "#39CCCC", "FO" = "#F012BE", "EP" = "#AAAAAA", "FA" = "#7FDBFF", "SC" = "#FF69B4" ) # Convert angle to radians angle_radians <- angle_degrees * (pi / 180) # Legend location based on handedness if(pitcher_hand == "L") { leg <- c(0.08, .22) factor <- -1 } else { leg <- c(0.92, .22) factor <- 1 } # Calculate the endpoint coordinates x_end <- 50 * cos(angle_radians) * factor y_end <- 50 * sin(angle_radians) avg_locations <- game %>% group_by(pitch_abbr) %>% summarize( avg_HB = mean(HB, na.rm = TRUE), avg_IVB = mean(IVB, na.rm = TRUE) ) # Get unique pitch types from the game data game_pitches <- unique(game$pitch_abbr) # Filter season data for the current arm angle, handedness, and only pitches in the game arm_angle_data <- data1 %>% filter(abs(round(arm_angle) - angle_degrees) <= 2, phand == pitcher_hand) %>% left_join(pitch_type_lookup, by = c("pitch_name")) %>% filter(pitch_abbr %in% game_pitches) ggplot(game, aes(x = HB, y = IVB)) + # Base layers geom_vline(xintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) + geom_hline(yintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) + # Add ellipses from season data for this arm angle stat_ellipse( data = arm_angle_data, aes(x = HB, y = IVB, fill = pitch_abbr), geom = "polygon", alpha = 0.2, level = 0.68, show.legend = FALSE ) + # Individual pitch points from the game # geom_point(aes(fill = pitch_abbr), color = "#c6c6c6", size = 3, stroke = .5, shape = 21) + # # Average location points from season data # geom_point( # data = AAaveragePT %>% # filter(arm_angle == angle_degrees, # phand == pitcher_hand) %>% # left_join(pitch_type_lookup, by = c("pitch_name")) %>% # filter(pitch_abbr %in% game_pitches), # aes(x = AvgHB, y = AvgIVB, fill = pitch_abbr), # color = "black", # size = 6, # stroke = .5, # shape = 21 # ) + geom_point(data = avg_locations, aes(x = avg_HB, y = avg_IVB, fill = pitch_abbr), color = "black", size = 6, stroke = .5, shape = 21) + # Arm angle line geom_segment(x = 0, y = 0, xend = x_end, yend = y_end, color = "red", linewidth = 1, linetype = 5) + # Aesthetics scale_fill_manual(values = pitch_colors) + scale_color_manual(values = pitch_colors) + labs( x = "Horizontal Break (in)", y = "Induced Vertical Break (in)", title = title, subtitle = paste0("Arm Angle: ", angle_degrees, "\u00b0"), caption = "Data: MLB | Viz: @TimStats\nEllipses show 1σ of movement for same-handed pitchers at similar arm angles" ) + xlim(-25, 25) + ylim(-25, 25) + theme_minimal() + theme( legend.position = leg, plot.title = element_text(hjust = 0.5, face = "bold", color = "white"), plot.subtitle = element_text(hjust = 0.5, face = "italic", color = "white"), aspect.ratio = 1, plot.background = element_rect(fill = "#333333", color = NA), panel.background = element_rect(fill = "#333333", color = NA), axis.text = element_text(color = "white"), axis.title = element_text(color = "white"), legend.background = element_rect(fill = "#333333"), legend.text = element_text(color = "white"), legend.title = element_blank(), plot.margin = margin(10, 5, 10, 5), axis.line = element_blank(), axis.ticks = element_line(color = "white"), panel.grid = element_blank(), legend.key = element_blank(), plot.caption = element_text( color = "white", hjust = 0.5 ), panel.border = element_blank() ) } break_plot_tot <- function(game,data1,sdate,edate) { game <- game %>% left_join(pitch_type_lookup, by = c("pitch_name")) title <- paste0(unique(game$`Pitcher Name`)," ",sdate," to ",edate) # Get pitcher's handedness pitcher_hand <- unique(game$phand) angle_degrees <- round(mean(game$arm_angle,na.rm = TRUE),digits = 1) # Calculate average movement by arm angle and pitch type from season data # AAaveragePT <- data1 %>% # mutate(arm_angle = round(arm_angle, digits = 0)) %>% # group_by(arm_angle, phand, pitch_name) %>% # summarise( # AvgIVB = mean(IVB, na.rm = TRUE), # AvgHB = mean(HB, na.rm = TRUE), # .groups = 'drop' # ) pitch_colors <- c( "FF" = "#FF4136", "SI" = "#FF851B", "FC" = "#FFDC00", "CH" = "#2ECC40", "SL" = "#0074D9", "ST" = "#ED68ED", "CU" = "#B10DC9", "FS" = "#01FF70", "KC" = "#85144b", "SV" = "#3D9970", "KN" = "#39CCCC", "FO" = "#F012BE", "EP" = "#AAAAAA", "FA" = "#7FDBFF", "SC" = "#FF69B4" ) # Convert angle to radians angle_radians <- angle_degrees * (pi / 180) # Legend location based on handedness if(pitcher_hand == "L") { leg <- c(0.08, .22) factor <- -1 } else { leg <- c(0.92, .22) factor <- 1 } # Calculate the endpoint coordinates x_end <- 50 * cos(angle_radians) * factor y_end <- 50 * sin(angle_radians) avg_locations <- game %>% group_by(pitch_abbr) %>% summarize( avg_HB = mean(HB, na.rm = TRUE), avg_IVB = mean(IVB, na.rm = TRUE) ) # Get unique pitch types from the game data game_pitches <- unique(game$pitch_abbr) # Filter season data for the current arm angle, handedness, and only pitches in the game arm_angle_data <- data1 %>% filter(abs(round(arm_angle) - angle_degrees) <= 2, phand == pitcher_hand) %>% left_join(pitch_type_lookup, by = c("pitch_name")) %>% filter(pitch_abbr %in% game_pitches) ggplot(game, aes(x = HB, y = IVB)) + # Base layers geom_vline(xintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) + geom_hline(yintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) + # Add ellipses from season data for this arm angle stat_ellipse( data = arm_angle_data, aes(x = HB, y = IVB, fill = pitch_abbr), geom = "polygon", alpha = 0.2, level = 0.68, show.legend = FALSE ) + # Individual pitch points from the game geom_point(aes(fill = pitch_abbr), color = "#c6c6c6", size = 3, stroke = .5, shape = 21) + # # Average location points from season data # geom_point( # data = AAaveragePT %>% # filter(arm_angle == angle_degrees, # phand == pitcher_hand) %>% # left_join(pitch_type_lookup, by = c("pitch_name")) %>% # filter(pitch_abbr %in% game_pitches), # aes(x = AvgHB, y = AvgIVB, fill = pitch_abbr), # color = "black", # size = 6, # stroke = .5, # shape = 21 # ) + geom_point(data = avg_locations, aes(x = avg_HB, y = avg_IVB, fill = pitch_abbr), color = "black", size = 6, stroke = .5, shape = 21) + # Arm angle line geom_segment(x = 0, y = 0, xend = x_end, yend = y_end, color = "red", linewidth = 1, linetype = 5) + # Aesthetics scale_fill_manual(values = pitch_colors) + scale_color_manual(values = pitch_colors) + labs( x = "Horizontal Break (in)", y = "Induced Vertical Break (in)", title = title, subtitle = paste0("Arm Angle: ", angle_degrees, "\u00b0"), caption = "Data: MLB | Viz: @TimStats\nEllipses show 1σ of movement for same-handed pitchers at similar arm angles" ) + xlim(-25, 25) + ylim(-25, 25) + theme_minimal() + theme( legend.position = leg, plot.title = element_text(hjust = 0.5, face = "bold", color = "white"), plot.subtitle = element_text(hjust = 0.5, face = "italic", color = "white"), aspect.ratio = 1, plot.background = element_rect(fill = "#333333", color = NA), panel.background = element_rect(fill = "#333333", color = NA), axis.text = element_text(color = "white"), axis.title = element_text(color = "white"), legend.background = element_rect(fill = "#333333"), legend.text = element_text(color = "white"), legend.title = element_blank(), plot.margin = margin(10, 5, 10, 5), axis.line = element_blank(), axis.ticks = element_line(color = "white"), panel.grid = element_blank(), legend.key = element_blank(), plot.caption = element_text( color = "white", hjust = 0.5 ), panel.border = element_blank() ) } ui <- fluidPage( # Application title titlePanel("2020-2024 MLB Pitch Plots"), sidebarLayout( sidebarPanel( width = 3, selectInput("player", "Select Player:", choices = unique(data1$`Pitcher Name`), width = "100%"), dateRangeInput("date1", "Date Range:", start = "2024-02-09", end = "2024-09-05", width = "100%"), radioButtons("type", "Plot Type", choices = c("Season Average","All Pitches")), actionButton("submit", "Update Plot", class = "btn btn-primary btn-block", style = "margin-bottom: 10px"), downloadButton("download", "Download Plot", class = "btn btn-success btn-block") ), mainPanel( width = 9, plotOutput("Plot") ) ) ) # Define server server <- function(input, output) { filtered_data <- eventReactive(input$submit, { data1 %>% filter(`Pitcher Name` == input$player, between(as.Date(date), input$date1[1], input$date1[2])) }) current_plot <- reactive({ game <- filtered_data() season_data <- data1 if(nrow(game) == 0) { return(ggplot() + annotate("text", x = 0.5, y = 0.5, label = "No data available for selected date range", color = "white") + theme_void() + theme(plot.background = element_rect(fill = "#333333", color = NA))) } if(input$type == "Season Average"){ break_plot_Szn(game, season_data, input$date1[1], input$date1[2]) } else{ break_plot_tot(game, season_data, input$date1[1], input$date1[2]) } }) output$Plot <- renderPlot({ current_plot() }, width = 1000, height = 1000) output$download <- downloadHandler( filename = function() { paste0( gsub(" ", "_", input$player), "_", format(input$date1[1], "%Y%m%d"), "_to_", format(input$date1[2], "%Y%m%d"), ".png" ) }, content = function(file) { ggsave(file, plot = current_plot(), width = 7, height = 7, dpi = 300, bg = "#333333") } ) } # Run the application shinyApp(ui = ui, server = server)