Spaces:
Build error
Build error
Update app.R
Browse files
app.R
CHANGED
|
@@ -2,28 +2,23 @@ library(shiny)
|
|
| 2 |
library(dplyr)
|
| 3 |
library(ggplot2)
|
| 4 |
library(utils)
|
|
|
|
|
|
|
| 5 |
|
| 6 |
data1 <- read.csv("data1.csv", header = TRUE, check.names = FALSE, fileEncoding = "UTF-8")
|
| 7 |
data1 <- data1[,2:54]
|
| 8 |
|
| 9 |
-
|
| 10 |
-
mutate(arm_angle = round(arm_angle, digits = 0)) %>%
|
| 11 |
-
group_by(arm_angle, phand, pitch_name) %>%
|
| 12 |
-
summarise(
|
| 13 |
-
AvgIVB = mean(IVB, na.rm = TRUE),
|
| 14 |
-
AvgHB = mean(HB, na.rm = TRUE),
|
| 15 |
-
.groups = 'drop'
|
| 16 |
-
)
|
| 17 |
-
|
| 18 |
pitch_type_lookup <- data.frame(
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
)
|
| 26 |
|
|
|
|
| 27 |
pitch_colors <- c(
|
| 28 |
"FF" = "#FF4136",
|
| 29 |
"SI" = "#FF851B",
|
|
@@ -43,8 +38,6 @@ pitch_colors <- c(
|
|
| 43 |
)
|
| 44 |
|
| 45 |
break_plot_Szn <- function(game, data1, sdate, edate) {
|
| 46 |
-
# Calculate pitch usage percentages
|
| 47 |
-
|
| 48 |
game <- game %>% filter(between(as.Date(date), sdate, edate))
|
| 49 |
total_pitches <- nrow(game)
|
| 50 |
usage_stats <- game %>%
|
|
@@ -58,7 +51,6 @@ break_plot_Szn <- function(game, data1, sdate, edate) {
|
|
| 58 |
left_join(pitch_type_lookup, by = c("pitch_name")) %>%
|
| 59 |
left_join(usage_stats, by = "pitch_name")
|
| 60 |
|
| 61 |
-
|
| 62 |
game <- game %>% filter(!is.na(pitch_abbr))
|
| 63 |
title <- paste0(unique(game$`Pitcher Name`)," ",sdate," to ",edate)
|
| 64 |
pitcher_hand <- unique(game$phand)
|
|
@@ -77,24 +69,21 @@ break_plot_Szn <- function(game, data1, sdate, edate) {
|
|
| 77 |
x_end <- 50 * cos(angle_radians) * factor
|
| 78 |
y_end <- 50 * sin(angle_radians)
|
| 79 |
|
| 80 |
-
# Create the base color mapping
|
| 81 |
game_pitches <- unique(game$pitch_abbr)
|
| 82 |
used_colors <- pitch_colors[game_pitches]
|
| 83 |
|
| 84 |
-
# Add usage to the game data
|
| 85 |
avg_locations <- game %>%
|
| 86 |
group_by(pitch_abbr) %>%
|
| 87 |
summarize(
|
| 88 |
avg_HB = mean(HB, na.rm = TRUE),
|
| 89 |
avg_IVB = mean(IVB, na.rm = TRUE),
|
| 90 |
-
|
|
|
|
| 91 |
)
|
| 92 |
|
| 93 |
-
# Create labels with percentages
|
| 94 |
legend_labels <- paste0(names(used_colors), " (",
|
| 95 |
avg_locations$usage[match(names(used_colors), avg_locations$pitch_abbr)], ")")
|
| 96 |
|
| 97 |
-
# Create the color scale with usage labels
|
| 98 |
fill_values <- used_colors
|
| 99 |
names(fill_values) <- legend_labels
|
| 100 |
|
|
@@ -105,10 +94,9 @@ break_plot_Szn <- function(game, data1, sdate, edate) {
|
|
| 105 |
filter(!is.na(pitch_abbr)) %>%
|
| 106 |
filter(pitch_abbr %in% game_pitches)
|
| 107 |
|
| 108 |
-
ggplot(game, aes(x = HB, y = IVB)) +
|
| 109 |
geom_vline(xintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
|
| 110 |
geom_hline(yintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
|
| 111 |
-
# Ellipses
|
| 112 |
stat_ellipse(
|
| 113 |
data = arm_angle_data,
|
| 114 |
aes(fill = paste0(pitch_abbr, " (",
|
|
@@ -118,17 +106,17 @@ break_plot_Szn <- function(game, data1, sdate, edate) {
|
|
| 118 |
level = 0.68,
|
| 119 |
show.legend = FALSE
|
| 120 |
) +
|
| 121 |
-
|
| 122 |
-
geom_point(
|
| 123 |
data = avg_locations,
|
| 124 |
aes(x = avg_HB, y = avg_IVB,
|
| 125 |
-
fill = paste0(pitch_abbr, " (", usage, ")")
|
|
|
|
|
|
|
| 126 |
color = "black",
|
| 127 |
size = 6,
|
| 128 |
stroke = .5,
|
| 129 |
shape = 21
|
| 130 |
) +
|
| 131 |
-
# Arm angle line
|
| 132 |
geom_segment(x = 0, y = 0, xend = x_end, yend = y_end,
|
| 133 |
color = "red", linewidth = 1, linetype = 5) +
|
| 134 |
scale_fill_manual(values = fill_values) +
|
|
@@ -159,16 +147,14 @@ break_plot_Szn <- function(game, data1, sdate, edate) {
|
|
| 159 |
axis.ticks = element_line(color = "white"),
|
| 160 |
panel.grid = element_blank(),
|
| 161 |
legend.key = element_blank(),
|
| 162 |
-
plot.caption = element_text(
|
| 163 |
-
color = "white",
|
| 164 |
-
hjust = 0.5
|
| 165 |
-
),
|
| 166 |
panel.border = element_blank()
|
| 167 |
)
|
|
|
|
|
|
|
| 168 |
}
|
| 169 |
|
| 170 |
break_plot_tot <- function(game, data1, sdate, edate) {
|
| 171 |
-
# Calculate pitch usage percentages
|
| 172 |
game <- game %>% filter(between(as.Date(date), sdate, edate))
|
| 173 |
total_pitches <- nrow(game)
|
| 174 |
usage_stats <- game %>%
|
|
@@ -177,11 +163,11 @@ break_plot_tot <- function(game, data1, sdate, edate) {
|
|
| 177 |
count = n(),
|
| 178 |
usage = sprintf("%.1f%%", (count/total_pitches) * 100)
|
| 179 |
)
|
| 180 |
-
|
| 181 |
game <- game %>%
|
| 182 |
left_join(pitch_type_lookup, by = c("pitch_name")) %>%
|
| 183 |
left_join(usage_stats, by = "pitch_name")
|
| 184 |
-
|
| 185 |
game <- game %>% filter(!is.na(pitch_abbr))
|
| 186 |
title <- paste0(unique(game$`Pitcher Name`)," ",sdate," to ",edate)
|
| 187 |
pitcher_hand <- unique(game$phand)
|
|
@@ -200,24 +186,21 @@ break_plot_tot <- function(game, data1, sdate, edate) {
|
|
| 200 |
x_end <- 50 * cos(angle_radians) * factor
|
| 201 |
y_end <- 50 * sin(angle_radians)
|
| 202 |
|
| 203 |
-
# Create the base color mapping
|
| 204 |
game_pitches <- unique(game$pitch_abbr)
|
| 205 |
used_colors <- pitch_colors[game_pitches]
|
| 206 |
|
| 207 |
-
# Add usage to the game data
|
| 208 |
avg_locations <- game %>%
|
| 209 |
group_by(pitch_abbr) %>%
|
| 210 |
summarize(
|
| 211 |
avg_HB = mean(HB, na.rm = TRUE),
|
| 212 |
avg_IVB = mean(IVB, na.rm = TRUE),
|
| 213 |
-
|
|
|
|
| 214 |
)
|
| 215 |
|
| 216 |
-
# Create labels with percentages
|
| 217 |
legend_labels <- paste0(names(used_colors), " (",
|
| 218 |
avg_locations$usage[match(names(used_colors), avg_locations$pitch_abbr)], ")")
|
| 219 |
|
| 220 |
-
# Create the color scale with usage labels
|
| 221 |
fill_values <- used_colors
|
| 222 |
names(fill_values) <- legend_labels
|
| 223 |
|
|
@@ -228,10 +211,9 @@ break_plot_tot <- function(game, data1, sdate, edate) {
|
|
| 228 |
filter(!is.na(pitch_abbr)) %>%
|
| 229 |
filter(pitch_abbr %in% game_pitches)
|
| 230 |
|
| 231 |
-
ggplot(game, aes(x = HB, y = IVB)) +
|
| 232 |
geom_vline(xintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
|
| 233 |
geom_hline(yintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
|
| 234 |
-
# Ellipses
|
| 235 |
stat_ellipse(
|
| 236 |
data = arm_angle_data,
|
| 237 |
aes(fill = paste0(pitch_abbr, " (",
|
|
@@ -241,25 +223,26 @@ break_plot_tot <- function(game, data1, sdate, edate) {
|
|
| 241 |
level = 0.68,
|
| 242 |
show.legend = FALSE
|
| 243 |
) +
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
|
|
|
| 247 |
color = "#c6c6c6",
|
| 248 |
size = 3,
|
| 249 |
stroke = .5,
|
| 250 |
shape = 21
|
| 251 |
) +
|
| 252 |
-
|
| 253 |
-
geom_point(
|
| 254 |
data = avg_locations,
|
| 255 |
aes(x = avg_HB, y = avg_IVB,
|
| 256 |
-
fill = paste0(pitch_abbr, " (", usage, ")")
|
|
|
|
|
|
|
| 257 |
color = "black",
|
| 258 |
size = 6,
|
| 259 |
stroke = .5,
|
| 260 |
shape = 21
|
| 261 |
) +
|
| 262 |
-
# Arm angle line
|
| 263 |
geom_segment(x = 0, y = 0, xend = x_end, yend = y_end,
|
| 264 |
color = "red", linewidth = 1, linetype = 5) +
|
| 265 |
scale_fill_manual(values = fill_values) +
|
|
@@ -290,53 +273,51 @@ break_plot_tot <- function(game, data1, sdate, edate) {
|
|
| 290 |
axis.ticks = element_line(color = "white"),
|
| 291 |
panel.grid = element_blank(),
|
| 292 |
legend.key = element_blank(),
|
| 293 |
-
plot.caption = element_text(
|
| 294 |
-
color = "white",
|
| 295 |
-
hjust = 0.5
|
| 296 |
-
),
|
| 297 |
panel.border = element_blank()
|
| 298 |
)
|
|
|
|
|
|
|
| 299 |
}
|
|
|
|
| 300 |
ui <- fluidPage(
|
| 301 |
-
#
|
|
|
|
| 302 |
titlePanel("2020-2024 MLB Pitch Plots"),
|
| 303 |
|
| 304 |
sidebarLayout(
|
| 305 |
sidebarPanel(
|
| 306 |
-
width = 3,
|
| 307 |
-
selectInput("player", "Select Player:",
|
| 308 |
-
|
| 309 |
-
|
|
|
|
|
|
|
| 310 |
start = "2024-03-20",
|
| 311 |
end = "2024-10-01",
|
| 312 |
-
width = "100%"),
|
| 313 |
radioButtons("type", "Plot Type",
|
| 314 |
-
choices = c("Season Average","All Pitches")),
|
| 315 |
actionButton("submit", "Update Plot",
|
| 316 |
-
class = "btn btn-primary btn-block",
|
| 317 |
-
style = "margin-bottom: 10px"),
|
| 318 |
-
downloadButton("download", "Download Plot",
|
| 319 |
-
class = "btn btn-success btn-block")
|
| 320 |
),
|
| 321 |
|
| 322 |
-
|
| 323 |
mainPanel(
|
| 324 |
-
width = 9,
|
| 325 |
-
|
| 326 |
)
|
| 327 |
)
|
| 328 |
)
|
| 329 |
-
|
| 330 |
-
# Define server
|
| 331 |
server <- function(input, output, session) {
|
| 332 |
-
# Create a reactive value to store current plot settings
|
| 333 |
plotSettings <- reactiveVal(list(
|
| 334 |
player = NULL,
|
| 335 |
type = "Season Average",
|
| 336 |
dates = c(as.Date("2024-03-20"), as.Date("2024-10-01"))
|
| 337 |
))
|
| 338 |
|
| 339 |
-
# Update settings only when submit is clicked
|
| 340 |
observeEvent(input$submit, {
|
| 341 |
plotSettings(list(
|
| 342 |
player = input$player,
|
|
@@ -345,28 +326,44 @@ server <- function(input, output, session) {
|
|
| 345 |
))
|
| 346 |
})
|
| 347 |
|
| 348 |
-
output$Plot <-
|
| 349 |
settings <- plotSettings()
|
| 350 |
-
req(settings$player)
|
| 351 |
|
| 352 |
game <- data1 %>%
|
| 353 |
filter(`Pitcher Name` == settings$player)
|
| 354 |
|
| 355 |
if(nrow(game) == 0) {
|
| 356 |
-
return(ggplot() +
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
}
|
| 363 |
|
| 364 |
-
if(settings$type == "Season Average"){
|
| 365 |
break_plot_Szn(game, data1, settings$dates[1], settings$dates[2])
|
| 366 |
-
} else{
|
| 367 |
break_plot_tot(game, data1, settings$dates[1], settings$dates[2])
|
| 368 |
}
|
| 369 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
|
| 371 |
output$download <- downloadHandler(
|
| 372 |
filename = function() {
|
|
@@ -383,9 +380,9 @@ server <- function(input, output, session) {
|
|
| 383 |
game <- data1 %>%
|
| 384 |
filter(`Pitcher Name` == settings$player)
|
| 385 |
|
| 386 |
-
plot <- if(settings$type == "Season Average"){
|
| 387 |
break_plot_Szn(game, data1, settings$dates[1], settings$dates[2])
|
| 388 |
-
} else{
|
| 389 |
break_plot_tot(game, data1, settings$dates[1], settings$dates[2])
|
| 390 |
}
|
| 391 |
|
|
@@ -394,9 +391,10 @@ server <- function(input, output, session) {
|
|
| 394 |
width = 10,
|
| 395 |
height = 10,
|
| 396 |
dpi = 300,
|
| 397 |
-
bg = "#333333")
|
| 398 |
}
|
| 399 |
)
|
| 400 |
}
|
| 401 |
|
|
|
|
| 402 |
shinyApp(ui = ui, server = server)
|
|
|
|
| 2 |
library(dplyr)
|
| 3 |
library(ggplot2)
|
| 4 |
library(utils)
|
| 5 |
+
library(ggiraph)
|
| 6 |
+
library(htmlwidgets)
|
| 7 |
|
| 8 |
data1 <- read.csv("data1.csv", header = TRUE, check.names = FALSE, fileEncoding = "UTF-8")
|
| 9 |
data1 <- data1[,2:54]
|
| 10 |
|
| 11 |
+
# Sample pitch type lookup - modify if needed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
pitch_type_lookup <- data.frame(
|
| 13 |
+
pitch_name = c("Changeup", "Curveball", "Cutter", "Eephus", "Forkball",
|
| 14 |
+
"Four-Seam Fastball", "Knuckle Ball", "Knuckle Curve",
|
| 15 |
+
"Screwball", "Sinker", "Slider", "Slurve", "Splitter", "Sweeper"),
|
| 16 |
+
pitch_abbr = c("CH", "CU", "FC", "EP", "FO", "FF", "KN", "KC",
|
| 17 |
+
"SC", "SI", "SL", "SV", "FS", "ST"),
|
| 18 |
+
stringsAsFactors = FALSE
|
| 19 |
)
|
| 20 |
|
| 21 |
+
# Define pitch colors
|
| 22 |
pitch_colors <- c(
|
| 23 |
"FF" = "#FF4136",
|
| 24 |
"SI" = "#FF851B",
|
|
|
|
| 38 |
)
|
| 39 |
|
| 40 |
break_plot_Szn <- function(game, data1, sdate, edate) {
|
|
|
|
|
|
|
| 41 |
game <- game %>% filter(between(as.Date(date), sdate, edate))
|
| 42 |
total_pitches <- nrow(game)
|
| 43 |
usage_stats <- game %>%
|
|
|
|
| 51 |
left_join(pitch_type_lookup, by = c("pitch_name")) %>%
|
| 52 |
left_join(usage_stats, by = "pitch_name")
|
| 53 |
|
|
|
|
| 54 |
game <- game %>% filter(!is.na(pitch_abbr))
|
| 55 |
title <- paste0(unique(game$`Pitcher Name`)," ",sdate," to ",edate)
|
| 56 |
pitcher_hand <- unique(game$phand)
|
|
|
|
| 69 |
x_end <- 50 * cos(angle_radians) * factor
|
| 70 |
y_end <- 50 * sin(angle_radians)
|
| 71 |
|
|
|
|
| 72 |
game_pitches <- unique(game$pitch_abbr)
|
| 73 |
used_colors <- pitch_colors[game_pitches]
|
| 74 |
|
|
|
|
| 75 |
avg_locations <- game %>%
|
| 76 |
group_by(pitch_abbr) %>%
|
| 77 |
summarize(
|
| 78 |
avg_HB = mean(HB, na.rm = TRUE),
|
| 79 |
avg_IVB = mean(IVB, na.rm = TRUE),
|
| 80 |
+
avg_velo = mean(start_speed, na.rm = TRUE),
|
| 81 |
+
usage = first(usage)
|
| 82 |
)
|
| 83 |
|
|
|
|
| 84 |
legend_labels <- paste0(names(used_colors), " (",
|
| 85 |
avg_locations$usage[match(names(used_colors), avg_locations$pitch_abbr)], ")")
|
| 86 |
|
|
|
|
| 87 |
fill_values <- used_colors
|
| 88 |
names(fill_values) <- legend_labels
|
| 89 |
|
|
|
|
| 94 |
filter(!is.na(pitch_abbr)) %>%
|
| 95 |
filter(pitch_abbr %in% game_pitches)
|
| 96 |
|
| 97 |
+
p <- ggplot(game, aes(x = HB, y = IVB)) +
|
| 98 |
geom_vline(xintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
|
| 99 |
geom_hline(yintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
|
|
|
|
| 100 |
stat_ellipse(
|
| 101 |
data = arm_angle_data,
|
| 102 |
aes(fill = paste0(pitch_abbr, " (",
|
|
|
|
| 106 |
level = 0.68,
|
| 107 |
show.legend = FALSE
|
| 108 |
) +
|
| 109 |
+
geom_point_interactive(
|
|
|
|
| 110 |
data = avg_locations,
|
| 111 |
aes(x = avg_HB, y = avg_IVB,
|
| 112 |
+
fill = paste0(pitch_abbr, " (", usage, ")"),
|
| 113 |
+
tooltip = sprintf("Avg Velo: %.1f mph\nAvg IVB: %.1f in\nAvg HB: %.1f in",
|
| 114 |
+
avg_velo, avg_IVB, avg_HB)),
|
| 115 |
color = "black",
|
| 116 |
size = 6,
|
| 117 |
stroke = .5,
|
| 118 |
shape = 21
|
| 119 |
) +
|
|
|
|
| 120 |
geom_segment(x = 0, y = 0, xend = x_end, yend = y_end,
|
| 121 |
color = "red", linewidth = 1, linetype = 5) +
|
| 122 |
scale_fill_manual(values = fill_values) +
|
|
|
|
| 147 |
axis.ticks = element_line(color = "white"),
|
| 148 |
panel.grid = element_blank(),
|
| 149 |
legend.key = element_blank(),
|
| 150 |
+
plot.caption = element_text(color = "white", hjust = 0.5),
|
|
|
|
|
|
|
|
|
|
| 151 |
panel.border = element_blank()
|
| 152 |
)
|
| 153 |
+
|
| 154 |
+
return(p)
|
| 155 |
}
|
| 156 |
|
| 157 |
break_plot_tot <- function(game, data1, sdate, edate) {
|
|
|
|
| 158 |
game <- game %>% filter(between(as.Date(date), sdate, edate))
|
| 159 |
total_pitches <- nrow(game)
|
| 160 |
usage_stats <- game %>%
|
|
|
|
| 163 |
count = n(),
|
| 164 |
usage = sprintf("%.1f%%", (count/total_pitches) * 100)
|
| 165 |
)
|
| 166 |
+
|
| 167 |
game <- game %>%
|
| 168 |
left_join(pitch_type_lookup, by = c("pitch_name")) %>%
|
| 169 |
left_join(usage_stats, by = "pitch_name")
|
| 170 |
+
|
| 171 |
game <- game %>% filter(!is.na(pitch_abbr))
|
| 172 |
title <- paste0(unique(game$`Pitcher Name`)," ",sdate," to ",edate)
|
| 173 |
pitcher_hand <- unique(game$phand)
|
|
|
|
| 186 |
x_end <- 50 * cos(angle_radians) * factor
|
| 187 |
y_end <- 50 * sin(angle_radians)
|
| 188 |
|
|
|
|
| 189 |
game_pitches <- unique(game$pitch_abbr)
|
| 190 |
used_colors <- pitch_colors[game_pitches]
|
| 191 |
|
|
|
|
| 192 |
avg_locations <- game %>%
|
| 193 |
group_by(pitch_abbr) %>%
|
| 194 |
summarize(
|
| 195 |
avg_HB = mean(HB, na.rm = TRUE),
|
| 196 |
avg_IVB = mean(IVB, na.rm = TRUE),
|
| 197 |
+
avg_velo = mean(start_speed, na.rm = TRUE),
|
| 198 |
+
usage = first(usage)
|
| 199 |
)
|
| 200 |
|
|
|
|
| 201 |
legend_labels <- paste0(names(used_colors), " (",
|
| 202 |
avg_locations$usage[match(names(used_colors), avg_locations$pitch_abbr)], ")")
|
| 203 |
|
|
|
|
| 204 |
fill_values <- used_colors
|
| 205 |
names(fill_values) <- legend_labels
|
| 206 |
|
|
|
|
| 211 |
filter(!is.na(pitch_abbr)) %>%
|
| 212 |
filter(pitch_abbr %in% game_pitches)
|
| 213 |
|
| 214 |
+
p <- ggplot(game, aes(x = HB, y = IVB)) +
|
| 215 |
geom_vline(xintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
|
| 216 |
geom_hline(yintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
|
|
|
|
| 217 |
stat_ellipse(
|
| 218 |
data = arm_angle_data,
|
| 219 |
aes(fill = paste0(pitch_abbr, " (",
|
|
|
|
| 223 |
level = 0.68,
|
| 224 |
show.legend = FALSE
|
| 225 |
) +
|
| 226 |
+
geom_point_interactive(
|
| 227 |
+
aes(fill = paste0(pitch_abbr, " (", usage, ")"),
|
| 228 |
+
tooltip = sprintf("Velo: %.1f mph\nIVB: %.1f in\nHB: %.1f in",
|
| 229 |
+
start_speed, IVB, HB)),
|
| 230 |
color = "#c6c6c6",
|
| 231 |
size = 3,
|
| 232 |
stroke = .5,
|
| 233 |
shape = 21
|
| 234 |
) +
|
| 235 |
+
geom_point_interactive(
|
|
|
|
| 236 |
data = avg_locations,
|
| 237 |
aes(x = avg_HB, y = avg_IVB,
|
| 238 |
+
fill = paste0(pitch_abbr, " (", usage, ")"),
|
| 239 |
+
tooltip = sprintf("Avg Velo: %.1f mph\nAvg IVB: %.1f in\nAvg HB: %.1f in",
|
| 240 |
+
avg_velo, avg_IVB, avg_HB)),
|
| 241 |
color = "black",
|
| 242 |
size = 6,
|
| 243 |
stroke = .5,
|
| 244 |
shape = 21
|
| 245 |
) +
|
|
|
|
| 246 |
geom_segment(x = 0, y = 0, xend = x_end, yend = y_end,
|
| 247 |
color = "red", linewidth = 1, linetype = 5) +
|
| 248 |
scale_fill_manual(values = fill_values) +
|
|
|
|
| 273 |
axis.ticks = element_line(color = "white"),
|
| 274 |
panel.grid = element_blank(),
|
| 275 |
legend.key = element_blank(),
|
| 276 |
+
plot.caption = element_text(color = "white", hjust = 0.5),
|
|
|
|
|
|
|
|
|
|
| 277 |
panel.border = element_blank()
|
| 278 |
)
|
| 279 |
+
|
| 280 |
+
return(p)
|
| 281 |
}
|
| 282 |
+
|
| 283 |
ui <- fluidPage(
|
| 284 |
+
# Remove useGirafe() line and just have girafeOutput in mainPanel
|
| 285 |
+
|
| 286 |
titlePanel("2020-2024 MLB Pitch Plots"),
|
| 287 |
|
| 288 |
sidebarLayout(
|
| 289 |
sidebarPanel(
|
| 290 |
+
width = 3,
|
| 291 |
+
selectInput("player", "Select Player:",
|
| 292 |
+
choices = unique(data1$`Pitcher Name`),
|
| 293 |
+
width = "100%"),
|
| 294 |
+
dateRangeInput("date1",
|
| 295 |
+
"Dates: (Be wary of multi year plots as arm angles may vary over years)",
|
| 296 |
start = "2024-03-20",
|
| 297 |
end = "2024-10-01",
|
| 298 |
+
width = "100%"),
|
| 299 |
radioButtons("type", "Plot Type",
|
| 300 |
+
choices = c("Season Average", "All Pitches")),
|
| 301 |
actionButton("submit", "Update Plot",
|
| 302 |
+
class = "btn btn-primary btn-block",
|
| 303 |
+
style = "margin-bottom: 10px"),
|
| 304 |
+
downloadButton("download", "Download Plot",
|
| 305 |
+
class = "btn btn-success btn-block")
|
| 306 |
),
|
| 307 |
|
|
|
|
| 308 |
mainPanel(
|
| 309 |
+
width = 9,
|
| 310 |
+
girafeOutput("Plot", width = "750px", height = "750px")
|
| 311 |
)
|
| 312 |
)
|
| 313 |
)
|
|
|
|
|
|
|
| 314 |
server <- function(input, output, session) {
|
|
|
|
| 315 |
plotSettings <- reactiveVal(list(
|
| 316 |
player = NULL,
|
| 317 |
type = "Season Average",
|
| 318 |
dates = c(as.Date("2024-03-20"), as.Date("2024-10-01"))
|
| 319 |
))
|
| 320 |
|
|
|
|
| 321 |
observeEvent(input$submit, {
|
| 322 |
plotSettings(list(
|
| 323 |
player = input$player,
|
|
|
|
| 326 |
))
|
| 327 |
})
|
| 328 |
|
| 329 |
+
output$Plot <- renderGirafe({
|
| 330 |
settings <- plotSettings()
|
| 331 |
+
req(settings$player)
|
| 332 |
|
| 333 |
game <- data1 %>%
|
| 334 |
filter(`Pitcher Name` == settings$player)
|
| 335 |
|
| 336 |
if(nrow(game) == 0) {
|
| 337 |
+
return(girafe(ggobj = ggplot() +
|
| 338 |
+
annotate("text", x = 0.5, y = 0.5,
|
| 339 |
+
label = "No data available for selected date range",
|
| 340 |
+
color = "white") +
|
| 341 |
+
theme_void() +
|
| 342 |
+
theme(plot.background = element_rect(fill = "#333333", color = NA))))
|
| 343 |
}
|
| 344 |
|
| 345 |
+
plot <- if(settings$type == "Season Average") {
|
| 346 |
break_plot_Szn(game, data1, settings$dates[1], settings$dates[2])
|
| 347 |
+
} else {
|
| 348 |
break_plot_tot(game, data1, settings$dates[1], settings$dates[2])
|
| 349 |
}
|
| 350 |
+
|
| 351 |
+
girafe(ggobj = plot,
|
| 352 |
+
width_svg = 10,
|
| 353 |
+
height_svg = 10,
|
| 354 |
+
options = list(
|
| 355 |
+
opts_tooltip(
|
| 356 |
+
opacity = 0.8,
|
| 357 |
+
css = "background-color: #333333; color: white; padding: 5px; border-radius: 3px;"
|
| 358 |
+
),
|
| 359 |
+
opts_hover(
|
| 360 |
+
css = "fill-opacity: 1; stroke: #FFF; stroke-width: 2;"
|
| 361 |
+
),
|
| 362 |
+
opts_selection(
|
| 363 |
+
type = "none"
|
| 364 |
+
)
|
| 365 |
+
))
|
| 366 |
+
})
|
| 367 |
|
| 368 |
output$download <- downloadHandler(
|
| 369 |
filename = function() {
|
|
|
|
| 380 |
game <- data1 %>%
|
| 381 |
filter(`Pitcher Name` == settings$player)
|
| 382 |
|
| 383 |
+
plot <- if(settings$type == "Season Average") {
|
| 384 |
break_plot_Szn(game, data1, settings$dates[1], settings$dates[2])
|
| 385 |
+
} else {
|
| 386 |
break_plot_tot(game, data1, settings$dates[1], settings$dates[2])
|
| 387 |
}
|
| 388 |
|
|
|
|
| 391 |
width = 10,
|
| 392 |
height = 10,
|
| 393 |
dpi = 300,
|
| 394 |
+
bg = "#333333")
|
| 395 |
}
|
| 396 |
)
|
| 397 |
}
|
| 398 |
|
| 399 |
+
# Run the application
|
| 400 |
shinyApp(ui = ui, server = server)
|