library(shiny) library(plotly) library(gridlayout) library(bslib) library(DT) library(rsconnect) library(baseballr) library(dplyr) library(tidyverse) library(rvest) library(ggplot2) library(janitor) library(ggthemes) library(ggpubr) library(jsonlite) library(utils) Sys.setenv(TZ='EST') is_barrel <- function(df) { df$barrel <- with(df, ifelse(hit_angle <= 50 & hit_speed >= 97 & hit_speed * 1.5 - hit_angle >= 117 & hit_speed + hit_angle >= 123, 1, 0)) return(df) } VAA <- function(milbtotal){ milbtotal <- milbtotal |> mutate(VAA = -atan((vz0+(az*(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))-vy0)/ ay))/(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))))*(180/pi)) } pitcher_summary <- function(game_pk,date){ gdate <- as.Date.character(date) gdate <- as.Date(gdate) tmilb <- mlb_pbp(game_pk) tmilb <- tmilb |> filter(type == "pitch") tmilb <- tmilb |> select(matchup.batter.fullName,matchup.batter.id,matchup.pitcher.fullName, matchup.pitcher.id,result.event,details.description,details.type.description, result.description,pitchData.startSpeed, pitchData.plateTime,pitchData.zone,pitchData.breaks.spinRate,pitchData.extension, pitchData.coordinates.pX,pitchData.coordinates.pZ,pitchData.coordinates.x0, pitchData.coordinates.y0,pitchData.coordinates.z0,pitchData.coordinates.aX, pitchData.coordinates.aY,pitchData.coordinates.aZ,pitchData.coordinates.vX0, pitchData.coordinates.vZ0,pitchData.coordinates.vY0,pitchData.coordinates.pfxX, pitchData.coordinates.pfxZ,pitchData.breaks.breakVerticalInduced,pitchData.breaks.breakHorizontal, hitData.launchSpeed,hitData.launchAngle,hitData.totalDistance,details.isInPlay,last.pitch.of.ab) colnames(tmilb) <- c("Batter Name","Batter ID","Pitcher Name","Pitcher ID", "result","description","pitch_name", "des","start_speed","plateTime","zone","spin_rate", "extension","px","pz","x0","y0","z0","ax","ay","az", "vx0","vz0","vy0","pfxX","pfxZ","IVB","HB","hit_speed", "hit_angle","hit_distance","inPlay","lastPitch") tmilb <- is_barrel(tmilb) tmilb <- tmilb |> mutate(is_strike_swinging = ifelse(description == "Swinging Strike" | description == "Foul Tip",TRUE,FALSE)) tmilb <- tmilb |> mutate(date = gdate) return(tmilb) } break_plot <- function(game){ ggplot()+ geom_point(game,mapping = aes(x=HB, y = IVB,color = pitch_name),size = 4) + geom_vline(xintercept = 0, color = "lightblue",linewidth = 1,linetype = 4)+ geom_hline(yintercept = 0, color = "lightblue",linewidth = 1,linetype = 4)+ xlab("Horizontal Break from Pitcher's Perspective (in) ")+ ylab("Induced Verical Break (in)") + xlim(-25,25) + ylim(-25,25) + ggthemes::theme_igray()+ theme(panel.grid.minor = element_line(color = "gray", size = 0.25, linetype = 1))+ guides(color = guide_legend(title = "Pitch Type")) } pitch_plot <- function(game){ ggplot()+ geom_point(game,mapping = aes(x=px, y = pz,color = pitch_name),size = 3.5) + geom_segment(aes(x=-0.71,xend = 0.71, y = 1.5,yend = 1.5))+ geom_segment(aes(x=-0.71,xend = 0.71, y = 3.6,yend = 3.6))+ geom_segment(aes(x= 0.71,xend = 0.71, y = 1.5,yend = 3.6))+ geom_segment(aes(x=-0.71,xend = -0.71, y = 1.5,yend =3.6))+ xlab(" ")+ ylab(" ") + xlim(-3,3)+ ylim(0.2,4)+ ggthemes::theme_few()+ guides(color = guide_legend(title = "Pitch Type"))+ theme(axis.text.x=element_blank(), axis.ticks.x=element_blank(), axis.text.y=element_blank(), axis.ticks.y=element_blank()) } summary_table <- function(game){ rows <- nrow(game) sumtable <- game |> mutate(VAA = -atan((vz0+(az*(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))-vy0)/ ay))/(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))))*(180/pi)) |> mutate(team_fielding_id = ifelse(description == "Called Strike" | description == "Swinging Strike" | description == "Swinging Strike (Blocked)",1,0))|> mutate(swing = ifelse(description == "Foul" | description == "Foul Pitchout" | description == "In play, no out" | description == "In play, out(s)" | description == "In play, run(s)" | description == "Swinging Strike" | description == "swinging Strike (Blocked)" | description == "Foul Tip",1,0)) |> mutate(is_strike_swinging = ifelse(is_strike_swinging == TRUE,1,0)) |> mutate(Pitch = pitch_name) |> group_by(Pitch)|> summarize(Pitches = n(), 'Pitch%' = round(sum(Pitches)/sum(rows) * 100,digits = 1), 'Avg. Velo' = round(sum(start_speed)/Pitches,digits = 1), 'Spin Rate' = round(sum(spin_rate)/ Pitches,digits = 1), 'Extension' = mean(extension,na.rm = TRUE), 'IVB' = mean(IVB,na.rm=TRUE), 'HB' = mean(HB,na.rm = TRUE), 'VAA' = mean(VAA,na.rm= TRUE), 'CSW%' = round(sum(team_fielding_id)/ sum(!is.na(team_fielding_id))*100,digits = 1), 'Whiff%' = round(sum(is_strike_swinging)/ sum(swing)*100,digits = 1)) } #MLB ID Stuff mlbid <- mlb_schedule(season = 2024, level_ids = "1") mlbteamH <- mlbid |> select(teams_home_team_name) mlbteamH <- distinct(mlbteamH) mlbteamA <- mlbid |> select(teams_away_team_name) mlbteamA <- distinct(mlbteamA) #AAA ID Stuff aaaid <- mlb_schedule(season = 2024, level_ids = "11") aaateamH <- aaaid |> select(teams_home_team_name) aaateamH <- distinct(aaateamH) aaateamA <- aaaid |> select(teams_away_team_name) aaateamA <- distinct(aaateamA) #FSL ID Stuff fslid <- mlb_schedule(season = 2024, level_ids = "14") fslid <- fslid |> filter(teams_home_team_name == "Daytona Tortugas" | teams_home_team_name == "Jupiter Hammerheads" | teams_home_team_name == "Palm Beach Cardinals" | teams_home_team_name == "St. Lucie Mets" | teams_home_team_name == "Bradenton Marauders" | teams_home_team_name == "Clearwater Threshers" | teams_home_team_name == "Dunedin Blue Jays" | teams_home_team_name == "Fort Myers Mighty Mussels" | teams_home_team_name == "Lakeland Flying Tigers" | teams_home_team_name == "Tampa Tarpons") fslteamH <- fslid |> select(teams_home_team_name) fslteamH <- distinct(fslteamH) fslteamA <- fslid |> select(teams_away_team_name) fslteamA <- distinct(fslteamA) #SB ID Stuff sbid <- mlb_schedule(season = 2024, level_ids = "21") sbteamH <- sbid |> select(teams_home_team_name) sbteamH <- distinct(sbteamH) sbteamA <- sbid |> select(teams_away_team_name) sbteamA <- distinct(sbteamA) ui <- grid_page( layout = c( "header header", "sidebar area2 " ), row_sizes = c( "40px", "1fr" ), col_sizes = c( "280px", "1fr" ), gap_size = "1rem", sidebarPanel( dateInput("date", "Date:"), selectizeInput("level","Level:",c("MLB","AAA","FSL","Spring Breakout")), selectizeInput("homeT","Home Team:",mlbteamH[,1]), selectizeInput("awayT", "Away Team:",mlbteamA[,1]), selectizeInput("gamenum","Game Number (For Doubleheaders):",c("1","2")), actionButton("update","Find Pitchers",icon("magnifying-glass"), style = "color: #FFFFFF; background-color: #0077B6"), selectizeInput("pitcher","Pitcher (For Spring Breakout if pitchers not loading, try changing game number)",c(" ")), selectizeInput("league","MLB or MiLB Photo",c("MLB","MiLB")), textInput("title","Card Title"), actionButton("update1","Make Card",icon("plus"), style = "color: #FFFFFF; background-color: #0077B6"), width = 15, ), grid_card_text( area = "header", content = "2024 MLB/AAA/FSL Summary Cards (BETA: Meant for Computers, Set Zoom to 100% for best use)", alignment = "start", is_title = FALSE ), grid_card( area = "area2", full_screen = TRUE, card_header(textOutput(outputId = "cardTitle")), card_body( gap = "0px", grid_container( layout = c( "area3 chart chart chart ", "breakgraph breakgraph strikezone strikezone" ), row_sizes = c( "1fr", "1fr" ), col_sizes = c( "1fr", "1fr", "1fr", "1fr" ), gap_size = "0px", grid_card_plot(area = "breakgraph"), grid_card_plot(area = "strikezone"), grid_card( area = "chart", card_body( tableOutput( outputId = "myTable" ), ) ), grid_card( area = "area3", plotOutput(outputId = "picture") ) ) ) ) ) server <- function(input, output) { # game <- reactive(pitcher_summary(input$game_pk)) # id <- reactive(as.character(game()[1,4])) observeEvent(input$level,{ if(input$level == "AAA"){ updateSelectizeInput(session = getDefaultReactiveDomain(),"homeT","Home Team:",choices = aaateamH[,1]) updateSelectizeInput(session = getDefaultReactiveDomain(),"awayT","Away Team:",choices = aaateamA[,1]) } if(input$level == "FSL"){ updateSelectizeInput(session = getDefaultReactiveDomain(),"homeT","Home Team:",choices = fslteamH[,1]) updateSelectizeInput(session = getDefaultReactiveDomain(),"awayT","Away Team:",choices = fslteamA[,1]) } if(input$level == "MLB"){ updateSelectizeInput(session = getDefaultReactiveDomain(),"homeT","Home Team:",choices = mlbteamH[,1]) updateSelectizeInput(session = getDefaultReactiveDomain(),"awayT","Away Team:",choices = mlbteamA[,1]) } if(input$level == "Spring Breakout"){ updateSelectizeInput(session = getDefaultReactiveDomain(),"homeT","Home Team:",choices = sbteamH[,1]) updateSelectizeInput(session = getDefaultReactiveDomain(),"awayT","Away Team:",choices = sbteamA[,1]) } }) observeEvent(input$update,{ if(input$level == "AAA"){ pname <- aaaid |> filter(date == input$date) |> filter(teams_home_team_name == input$homeT) |> filter(teams_away_team_name == input$awayT) |> filter(game_number == input$gamenum) pname <- tryCatch({pitcher_summary(pname[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")}) updateSelectizeInput(session = getDefaultReactiveDomain(),"pitcher","Pitcher:(For Spring Breakout if pitchers not loading, try changing game number)",choices = pname[,3]) } if(input$level == "FSL"){ pname <- fslid |> filter(date == input$date) |> filter(teams_home_team_name == input$homeT) |> filter(teams_away_team_name == input$awayT) |> filter(game_number == input$gamenum) pname <- tryCatch({pitcher_summary(pname[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")}) updateSelectizeInput(session = getDefaultReactiveDomain(),"pitcher","Pitcher:(For Spring Breakout if pitchers not loading, try changing game number)",choices = pname[,3]) } if(input$level == "MLB"){ pname <- mlbid |> filter(date == as.character.Date(input$date)) |> filter(teams_home_team_name == input$homeT) |> filter(teams_away_team_name == input$awayT) |> filter(game_number == input$gamenum) pname <- tryCatch({pitcher_summary(pname[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")}) updateSelectizeInput(session = getDefaultReactiveDomain(),"pitcher","Pitcher:(For Spring Breakout if pitchers not loading, try changing game number)",choices = pname[,3]) } if(input$level == "Spring Breakout"){ pname <- sbid |> filter(date == input$date) |> filter(teams_home_team_name == input$homeT) |> filter(teams_away_team_name == input$awayT) |> filter(game_number == input$gamenum) pname <- tryCatch({pitcher_summary(pname[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")}) updateSelectizeInput(session = getDefaultReactiveDomain(),"pitcher","Pitcher:(For Spring Breakout if pitchers not loading, try changing game number)",choices = pname[,3]) } }) observeEvent(input$update1,{ if(input$level == "MLB"){ id <- mlbid |> filter(date == as.character.Date(input$date)) |> filter(teams_home_team_name == input$homeT) |> filter(teams_away_team_name == input$awayT) |> filter(game_number == input$gamenum) game <- tryCatch({pitcher_summary(id[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")}) game <- game |> filter(`Pitcher Name` == input$pitcher) id <- as.character(game[1,4]) } if(input$level == "FSL"){ id <- fslid |> filter(date == as.character.Date(input$date)) |> filter(teams_home_team_name == input$homeT) |> filter(teams_away_team_name == input$awayT) |> filter(game_number == input$gamenum) game <- tryCatch({pitcher_summary(id[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")}) game <- game |> filter(`Pitcher Name` == input$pitcher) id <- as.character(game[1,4]) } if(input$level == "AAA"){ id <- aaaid |> filter(date == as.character.Date(input$date)) |> filter(teams_home_team_name == input$homeT) |> filter(teams_away_team_name == input$awayT) |> filter(game_number == input$gamenum) game <- tryCatch({pitcher_summary(id[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")}) game <- game |> filter(`Pitcher Name` == input$pitcher) id <- as.character(game[1,4]) } if(input$level == "Spring Breakout"){ id <- sbid |> filter(date == as.character.Date(input$date)) |> filter(teams_home_team_name == input$homeT) |> filter(teams_away_team_name == input$awayT) |> filter(game_number == input$gamenum) game <- tryCatch({pitcher_summary(id[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")}) game <- game |> filter(`Pitcher Name` == input$pitcher) id <- as.character(game[1,4]) } output$breakgraph <- renderPlot({ break_plot(game) }) output$strikezone <- renderPlot({ pitch_plot(game) }) output$myTable <- renderTable({ t <- summary_table(game) return(t) },width = '1150px',digits = 1,na = "NA",align = 'left') output$picture <- renderImage({ if(input$league == "MLB"){ y <- paste("https://midfield.mlbstatic.com/v1/people/","/mlb/300?circle=false",sep = id) } if(input$league == "MiLB"){ y <- paste("https://midfield.mlbstatic.com/v1/people/","/milb/300?circle=false",sep = id) } photo <- tempfile() download.file(y,photo,mode = 'wb') list( src = photo ) },deleteFile = TRUE) output$cardTitle <- renderText( title <- paste(input$title,"Made by @TimStats", sep = " ") ) }) } shinyApp(ui, server)