Spaces:
Running
Running
Update app.R
Browse files
app.R
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
# app.R
|
| 2 |
library(shiny)
|
| 3 |
library(dplyr)
|
| 4 |
library(ggplot2)
|
|
@@ -1025,378 +1024,6 @@ create_pitcher_game_line <- function(game_data) {
|
|
| 1025 |
)
|
| 1026 |
}
|
| 1027 |
|
| 1028 |
-
# =====================================================================
|
| 1029 |
-
# ================== ADVANCED PITCHER REPORT CODE ===================
|
| 1030 |
-
# =====================================================================
|
| 1031 |
-
|
| 1032 |
-
# Color palettes for heatmap tables
|
| 1033 |
-
make_color_fn <- function(min_val, max_val) {
|
| 1034 |
-
function(x) {
|
| 1035 |
-
normalized <- (x - min_val) / (max_val - min_val)
|
| 1036 |
-
normalized <- pmax(0, pmin(1, normalized))
|
| 1037 |
-
rgb(colorRamp(c("#648FFF", "#FFFFFF", "#FFB000"))(normalized), maxColorValue = 255)
|
| 1038 |
-
}
|
| 1039 |
-
}
|
| 1040 |
-
|
| 1041 |
-
make_color_fn_reverse <- function(min_val, max_val) {
|
| 1042 |
-
function(x) {
|
| 1043 |
-
normalized <- (x - min_val) / (max_val - min_val)
|
| 1044 |
-
normalized <- pmax(0, pmin(1, normalized))
|
| 1045 |
-
rgb(colorRamp(c("#FFB000", "#FFFFFF", "#648FFF"))(normalized), maxColorValue = 255)
|
| 1046 |
-
}
|
| 1047 |
-
}
|
| 1048 |
-
|
| 1049 |
-
# Enhanced pitcher summary table with color coding
|
| 1050 |
-
create_advanced_pitcher_summary <- function(data, player_name) {
|
| 1051 |
-
pitcher_data <- data %>% filter(Pitcher == player_name)
|
| 1052 |
-
|
| 1053 |
-
# Calculate comprehensive statistics
|
| 1054 |
-
summary_stats <- pitcher_data %>%
|
| 1055 |
-
summarise(
|
| 1056 |
-
BF = n_distinct(paste(Inning, Batter, PAofInning)),
|
| 1057 |
-
P = n(),
|
| 1058 |
-
K = sum(KorBB == "Strikeout", na.rm = TRUE),
|
| 1059 |
-
BB = sum(WalkIndicator, na.rm = TRUE),
|
| 1060 |
-
H = sum(PlayResult %in% c('Single','Double','Triple','HomeRun'), na.rm = TRUE),
|
| 1061 |
-
XBH = sum(PlayResult %in% c('Double','Triple','HomeRun'), na.rm = TRUE),
|
| 1062 |
-
HR = sum(PlayResult == 'HomeRun', na.rm = TRUE),
|
| 1063 |
-
HBP = sum(HBPIndicator, na.rm = TRUE),
|
| 1064 |
-
`Strike%` = 100 * sum(!PitchCall %in% c("BallCalled", "HitByPitch", "BallinDirt", "BallIntentional")) / n(),
|
| 1065 |
-
`CSW%` = 100 * sum(PitchCall %in% c("StrikeCalled", "StrikeSwinging")) / n(),
|
| 1066 |
-
`Whiff%` = 100 * sum(WhiffIndicator, na.rm = TRUE) / sum(SwingIndicator, na.rm = TRUE),
|
| 1067 |
-
`Chase%` = 100 * sum(Chaseindicator, na.rm = TRUE) / sum(SwingIndicator == 1 & StrikeZoneIndicator == 0, na.rm = TRUE),
|
| 1068 |
-
`Zone%` = 100 * sum(StrikeZoneIndicator, na.rm = TRUE) / n(),
|
| 1069 |
-
`In-Zone Whiff%` = 100 * sum(Zwhiffind, na.rm = TRUE) / sum(Zswing, na.rm = TRUE),
|
| 1070 |
-
.groups = "drop"
|
| 1071 |
-
)
|
| 1072 |
-
|
| 1073 |
-
return(summary_stats)
|
| 1074 |
-
}
|
| 1075 |
-
|
| 1076 |
-
# Pitch characteristics table with movement and location metrics
|
| 1077 |
-
create_advanced_pitch_characteristics <- function(data, player_name) {
|
| 1078 |
-
pitcher_data <- data %>%
|
| 1079 |
-
filter(Pitcher == player_name, !is.na(TaggedPitchType), TaggedPitchType != "Other")
|
| 1080 |
-
|
| 1081 |
-
pitch_stats <- pitcher_data %>%
|
| 1082 |
-
group_by(Pitch = TaggedPitchType) %>%
|
| 1083 |
-
summarise(
|
| 1084 |
-
Count = n(),
|
| 1085 |
-
`Usage%` = 100 * n() / nrow(pitcher_data),
|
| 1086 |
-
`Avg Velo` = round(mean(RelSpeed, na.rm = TRUE), 1),
|
| 1087 |
-
`Max Velo` = round(max(RelSpeed, na.rm = TRUE), 1),
|
| 1088 |
-
`Avg Spin` = round(mean(SpinRate, na.rm = TRUE), 0),
|
| 1089 |
-
`Max Spin` = round(max(SpinRate, na.rm = TRUE), 0),
|
| 1090 |
-
`Avg IVB` = round(mean(InducedVertBreak, na.rm = TRUE), 1),
|
| 1091 |
-
`Avg HB` = round(mean(HorzBreak, na.rm = TRUE), 1),
|
| 1092 |
-
`hRel` = round(mean(RelSide, na.rm = TRUE), 1),
|
| 1093 |
-
`vRel` = round(mean(RelHeight, na.rm = TRUE), 1),
|
| 1094 |
-
`Ext` = round(mean(Extension, na.rm = TRUE), 1),
|
| 1095 |
-
`Strike%` = 100 * sum(!PitchCall %in% c("BallCalled", "BallinDirt", "BallIntentional")) / n(),
|
| 1096 |
-
`Whiff%` = 100 * sum(WhiffIndicator, na.rm = TRUE) / sum(SwingIndicator, na.rm = TRUE),
|
| 1097 |
-
`Zone%` = 100 * sum(StrikeZoneIndicator, na.rm = TRUE) / n(),
|
| 1098 |
-
`Chase%` = 100 * sum(Chaseindicator, na.rm = TRUE) / sum(SwingIndicator == 1 & StrikeZoneIndicator == 0, na.rm = TRUE),
|
| 1099 |
-
.groups = "drop"
|
| 1100 |
-
) %>%
|
| 1101 |
-
arrange(desc(`Usage%`))
|
| 1102 |
-
|
| 1103 |
-
return(pitch_stats)
|
| 1104 |
-
}
|
| 1105 |
-
|
| 1106 |
-
# Enhanced break plot with pitcher handedness
|
| 1107 |
-
create_advanced_break_plot <- function(data, player_name, pitch_colors) {
|
| 1108 |
-
pitcher_data <- data %>%
|
| 1109 |
-
filter(Pitcher == player_name, !is.na(TaggedPitchType), TaggedPitchType != "Other",
|
| 1110 |
-
!is.na(HorzBreak), !is.na(InducedVertBreak))
|
| 1111 |
-
|
| 1112 |
-
if (nrow(pitcher_data) == 0) {
|
| 1113 |
-
return(ggplot() + theme_void() + ggtitle("Pitch Movement") +
|
| 1114 |
-
theme(plot.title = element_text(size = 14, face = "bold", hjust = 0.5)))
|
| 1115 |
-
}
|
| 1116 |
-
|
| 1117 |
-
# Determine pitcher handedness
|
| 1118 |
-
pitcher_hand <- pitcher_data$PitcherThrows[1]
|
| 1119 |
-
x_mult <- if (pitcher_hand == "Right") -1 else 1
|
| 1120 |
-
|
| 1121 |
-
# Calculate pitch averages for larger markers
|
| 1122 |
-
pitch_avgs <- pitcher_data %>%
|
| 1123 |
-
group_by(TaggedPitchType) %>%
|
| 1124 |
-
summarise(
|
| 1125 |
-
HB_avg = mean(HorzBreak * x_mult, na.rm = TRUE),
|
| 1126 |
-
IVB_avg = mean(InducedVertBreak, na.rm = TRUE),
|
| 1127 |
-
Velo_avg = round(mean(RelSpeed, na.rm = TRUE)),
|
| 1128 |
-
.groups = "drop"
|
| 1129 |
-
)
|
| 1130 |
-
|
| 1131 |
-
p <- ggplot() +
|
| 1132 |
-
geom_hline(yintercept = 0, color = "#808080", alpha = 0.5, linetype = "dashed") +
|
| 1133 |
-
geom_vline(xintercept = 0, color = "#808080", alpha = 0.5, linetype = "dashed") +
|
| 1134 |
-
geom_point(data = pitcher_data,
|
| 1135 |
-
aes(x = HorzBreak * x_mult, y = InducedVertBreak, fill = TaggedPitchType),
|
| 1136 |
-
shape = 21, size = 4, color = "black", stroke = 0.4, alpha = 0.85) +
|
| 1137 |
-
geom_point(data = pitch_avgs,
|
| 1138 |
-
aes(x = HB_avg, y = IVB_avg, fill = TaggedPitchType),
|
| 1139 |
-
shape = 21, size = 8, color = "black", stroke = 0.5, alpha = 1) +
|
| 1140 |
-
geom_text(data = pitch_avgs,
|
| 1141 |
-
aes(x = HB_avg, y = IVB_avg, label = Velo_avg),
|
| 1142 |
-
size = 4, fontface = "bold") +
|
| 1143 |
-
scale_fill_manual(values = pitch_colors, name = "Pitch Type") +
|
| 1144 |
-
coord_fixed(xlim = c(-25, 25), ylim = c(-25, 25)) +
|
| 1145 |
-
labs(title = "Pitch Movement",
|
| 1146 |
-
x = "Horizontal Break (in)",
|
| 1147 |
-
y = "Induced Vertical Break (in)") +
|
| 1148 |
-
theme_minimal(base_size = 12) +
|
| 1149 |
-
theme(
|
| 1150 |
-
plot.title = element_text(size = 16, face = "bold", hjust = 0.5),
|
| 1151 |
-
legend.position = "none",
|
| 1152 |
-
panel.grid.minor = element_blank(),
|
| 1153 |
-
aspect.ratio = 1
|
| 1154 |
-
)
|
| 1155 |
-
|
| 1156 |
-
# Add side labels based on handedness
|
| 1157 |
-
if (pitcher_hand == "Right") {
|
| 1158 |
-
p <- p +
|
| 1159 |
-
annotate("text", x = -24, y = -24, label = "← Glove Side",
|
| 1160 |
-
hjust = 0, vjust = 0, size = 3, fontface = "italic",
|
| 1161 |
-
color = "black") +
|
| 1162 |
-
annotate("text", x = 24, y = -24, label = "Arm Side →",
|
| 1163 |
-
hjust = 1, vjust = 0, size = 3, fontface = "italic",
|
| 1164 |
-
color = "black")
|
| 1165 |
-
} else {
|
| 1166 |
-
p <- p +
|
| 1167 |
-
annotate("text", x = 24, y = -24, label = "← Arm Side",
|
| 1168 |
-
hjust = 1, vjust = 0, size = 3, fontface = "italic",
|
| 1169 |
-
color = "black") +
|
| 1170 |
-
annotate("text", x = -24, y = -24, label = "Glove Side →",
|
| 1171 |
-
hjust = 0, vjust = 0, size = 3, fontface = "italic",
|
| 1172 |
-
color = "black")
|
| 1173 |
-
}
|
| 1174 |
-
|
| 1175 |
-
return(p)
|
| 1176 |
-
}
|
| 1177 |
-
|
| 1178 |
-
# Velocity distribution KDE plots by pitch type
|
| 1179 |
-
create_velocity_distribution_plot <- function(data, player_name, pitch_colors) {
|
| 1180 |
-
pitcher_data <- data %>%
|
| 1181 |
-
filter(Pitcher == player_name, !is.na(TaggedPitchType), TaggedPitchType != "Other",
|
| 1182 |
-
!is.na(RelSpeed))
|
| 1183 |
-
|
| 1184 |
-
if (nrow(pitcher_data) == 0) {
|
| 1185 |
-
return(ggplot() + theme_void() + ggtitle("Velocity Distribution") +
|
| 1186 |
-
theme(plot.title = element_text(size = 14, face = "bold", hjust = 0.5)))
|
| 1187 |
-
}
|
| 1188 |
-
|
| 1189 |
-
# Order pitch types by frequency
|
| 1190 |
-
pitch_order <- pitcher_data %>%
|
| 1191 |
-
count(TaggedPitchType, sort = TRUE) %>%
|
| 1192 |
-
pull(TaggedPitchType)
|
| 1193 |
-
|
| 1194 |
-
pitcher_data <- pitcher_data %>%
|
| 1195 |
-
mutate(TaggedPitchType = factor(TaggedPitchType, levels = pitch_order))
|
| 1196 |
-
|
| 1197 |
-
# Calculate means for each pitch type
|
| 1198 |
-
pitch_means <- pitcher_data %>%
|
| 1199 |
-
group_by(TaggedPitchType) %>%
|
| 1200 |
-
summarise(mean_velo = mean(RelSpeed, na.rm = TRUE), .groups = "drop")
|
| 1201 |
-
|
| 1202 |
-
ggplot(pitcher_data, aes(x = RelSpeed, fill = TaggedPitchType)) +
|
| 1203 |
-
geom_density(alpha = 0.7, color = "black", size = 0.3) +
|
| 1204 |
-
geom_vline(data = pitch_means, aes(xintercept = mean_velo, color = TaggedPitchType),
|
| 1205 |
-
linetype = "dashed", size = 0.8) +
|
| 1206 |
-
facet_wrap(~ TaggedPitchType, ncol = 1, strip.position = "left") +
|
| 1207 |
-
scale_fill_manual(values = pitch_colors) +
|
| 1208 |
-
scale_color_manual(values = pitch_colors) +
|
| 1209 |
-
labs(title = "Velocity Distribution by Pitch Type",
|
| 1210 |
-
x = "Velocity (mph)",
|
| 1211 |
-
y = "") +
|
| 1212 |
-
theme_minimal(base_size = 11) +
|
| 1213 |
-
theme(
|
| 1214 |
-
plot.title = element_text(size = 14, face = "bold", hjust = 0.5),
|
| 1215 |
-
legend.position = "none",
|
| 1216 |
-
strip.text.y.left = element_text(angle = 0, hjust = 1, face = "bold", size = 10),
|
| 1217 |
-
strip.placement = "outside",
|
| 1218 |
-
panel.grid.major.y = element_blank(),
|
| 1219 |
-
panel.grid.minor = element_blank(),
|
| 1220 |
-
axis.text.y = element_blank(),
|
| 1221 |
-
axis.ticks.y = element_blank()
|
| 1222 |
-
)
|
| 1223 |
-
}
|
| 1224 |
-
|
| 1225 |
-
# Pitch locations by batter side
|
| 1226 |
-
create_location_by_side_plot <- function(data, player_name, batter_side, pitch_colors) {
|
| 1227 |
-
pitcher_data <- data %>%
|
| 1228 |
-
filter(Pitcher == player_name, BatterSide == batter_side,
|
| 1229 |
-
!is.na(TaggedPitchType), TaggedPitchType != "Other",
|
| 1230 |
-
!is.na(PlateLocSide), !is.na(PlateLocHeight))
|
| 1231 |
-
|
| 1232 |
-
if (nrow(pitcher_data) == 0) {
|
| 1233 |
-
return(ggplot() +
|
| 1234 |
-
annotate("rect", xmin = -0.83, xmax = 0.83, ymin = 1.5, ymax = 3.38,
|
| 1235 |
-
fill = NA, color = "black", size = 0.5) +
|
| 1236 |
-
theme_void() +
|
| 1237 |
-
ggtitle(paste0("Locations vs ", batter_side, "HB")) +
|
| 1238 |
-
theme(plot.title = element_text(size = 12, face = "bold", hjust = 0.5)))
|
| 1239 |
-
}
|
| 1240 |
-
|
| 1241 |
-
# Count pitches by type
|
| 1242 |
-
pitch_count <- n_distinct(pitcher_data$TaggedPitchType)
|
| 1243 |
-
|
| 1244 |
-
ggplot(pitcher_data, aes(x = -PlateLocSide, y = PlateLocHeight, fill = TaggedPitchType)) +
|
| 1245 |
-
geom_point(alpha = 0.8, shape = 21, color = "black", stroke = 0.5, size = 4) +
|
| 1246 |
-
facet_wrap(~ TaggedPitchType, ncol = pitch_count,
|
| 1247 |
-
labeller = labeller(TaggedPitchType = ~ "")) +
|
| 1248 |
-
annotate("rect", xmin = -0.83, xmax = 0.83, ymin = 1.5, ymax = 3.38,
|
| 1249 |
-
fill = NA, color = "black", size = 0.5) +
|
| 1250 |
-
geom_segment(aes(x = -0.708, y = 0.15, xend = 0.708, yend = 0.15), color = "black", size = 0.5) +
|
| 1251 |
-
geom_segment(aes(x = -0.708, y = 0.30, xend = -0.708, yend = 0.15), color = "black", size = 0.5) +
|
| 1252 |
-
geom_segment(aes(x = 0.708, y = 0.30, xend = 0.708, yend = 0.15), color = "black", size = 0.5) +
|
| 1253 |
-
geom_segment(aes(x = -0.708, y = 0.30, xend = 0.000, yend = 0.50), color = "black", size = 0.5) +
|
| 1254 |
-
geom_segment(aes(x = 0.708, y = 0.30, xend = 0.000, yend = 0.50), color = "black", size = 0.5) +
|
| 1255 |
-
scale_fill_manual(values = pitch_colors) +
|
| 1256 |
-
scale_x_continuous(limits = c(-2, 2)) +
|
| 1257 |
-
scale_y_continuous(limits = c(0, 4.5)) +
|
| 1258 |
-
coord_fixed() +
|
| 1259 |
-
ggtitle(paste0("Pitch Locations vs ", batter_side, "HB")) +
|
| 1260 |
-
theme_void() +
|
| 1261 |
-
theme(
|
| 1262 |
-
plot.title = element_text(size = 12, face = "bold", hjust = 0.5),
|
| 1263 |
-
legend.position = "none",
|
| 1264 |
-
strip.text = element_text(size = 10, face = "bold"),
|
| 1265 |
-
strip.placement = "outside"
|
| 1266 |
-
)
|
| 1267 |
-
}
|
| 1268 |
-
|
| 1269 |
-
# Create the comprehensive advanced pitcher PDF
|
| 1270 |
-
create_advanced_pitcher_pdf <- function(game_df, pitcher_name, output_file) {
|
| 1271 |
-
if (length(dev.list()) > 0) try(dev.off(), silent = TRUE)
|
| 1272 |
-
|
| 1273 |
-
pitch_colors <- c(
|
| 1274 |
-
"Fastball" = "#FA8072", "Four-Seam" = "#FA8072", "Sinker" = "#fdae61",
|
| 1275 |
-
"Slider" = "#A020F0", "Sweeper" = "magenta", "Curveball" = "#2c7bb6",
|
| 1276 |
-
"ChangeUp" = "#90EE90", "Splitter" = "#90EE32", "Cutter" = "red"
|
| 1277 |
-
)
|
| 1278 |
-
|
| 1279 |
-
# Filter to pitcher's data
|
| 1280 |
-
pitcher_df <- game_df %>% filter(Pitcher == pitcher_name)
|
| 1281 |
-
if (nrow(pitcher_df) == 0) {
|
| 1282 |
-
pdf(output_file, width = 11, height = 17)
|
| 1283 |
-
grid.newpage()
|
| 1284 |
-
grid.text("No data available for this pitcher",
|
| 1285 |
-
gp = gpar(fontsize = 16, fontface = "bold"))
|
| 1286 |
-
dev.off()
|
| 1287 |
-
return(output_file)
|
| 1288 |
-
}
|
| 1289 |
-
|
| 1290 |
-
game_day <- parse_game_day(pitcher_df)
|
| 1291 |
-
|
| 1292 |
-
# Generate all components
|
| 1293 |
-
summary_stats <- create_advanced_pitcher_summary(pitcher_df, pitcher_name)
|
| 1294 |
-
pitch_char <- create_advanced_pitch_characteristics(pitcher_df, pitcher_name)
|
| 1295 |
-
|
| 1296 |
-
break_plot <- create_advanced_break_plot(pitcher_df, pitcher_name, pitch_colors)
|
| 1297 |
-
velo_plot <- create_velocity_distribution_plot(pitcher_df, pitcher_name, pitch_colors)
|
| 1298 |
-
location_lhb <- create_location_by_side_plot(pitcher_df, pitcher_name, "Left", pitch_colors)
|
| 1299 |
-
location_rhb <- create_location_by_side_plot(pitcher_df, pitcher_name, "Right", pitch_colors)
|
| 1300 |
-
|
| 1301 |
-
# Create PDF
|
| 1302 |
-
pdf(output_file, width = 11, height = 17)
|
| 1303 |
-
on.exit(try(dev.off(), silent = TRUE), add = TRUE)
|
| 1304 |
-
|
| 1305 |
-
grid.newpage()
|
| 1306 |
-
|
| 1307 |
-
# Header
|
| 1308 |
-
pushViewport(viewport(x = 0.5, y = 0.97, width = 1, height = 0.06, just = c("center", "top")))
|
| 1309 |
-
grid.text(paste(pitcher_name, "- Advanced Pitcher Report -", format(game_day, "%m/%d/%y")),
|
| 1310 |
-
gp = gpar(fontface = "bold", cex = 2.0, col = "#006F71"))
|
| 1311 |
-
popViewport()
|
| 1312 |
-
|
| 1313 |
-
# Game Summary Table
|
| 1314 |
-
grid.text("Game Summary", x = 0.5, y = 0.90,
|
| 1315 |
-
gp = gpar(fontface = "bold", cex = 1.2, col = "#006F71"))
|
| 1316 |
-
|
| 1317 |
-
summary_headers <- names(summary_stats)
|
| 1318 |
-
summary_values <- as.numeric(summary_stats[1,])
|
| 1319 |
-
summary_widths <- rep(0.06, length(summary_headers))
|
| 1320 |
-
|
| 1321 |
-
x_start <- 0.5 - sum(summary_widths)/2
|
| 1322 |
-
x_pos <- c(x_start, x_start + cumsum(summary_widths[-length(summary_widths)]))
|
| 1323 |
-
y_top <- 0.88
|
| 1324 |
-
row_h <- 0.022
|
| 1325 |
-
|
| 1326 |
-
# Draw summary table
|
| 1327 |
-
for (i in seq_along(summary_headers)) {
|
| 1328 |
-
grid.rect(x = x_pos[i], y = y_top, width = summary_widths[i] * 0.985, height = row_h,
|
| 1329 |
-
just = c("left", "top"), gp = gpar(fill = "#006F71", col = "black", lwd = 0.5))
|
| 1330 |
-
grid.text(summary_headers[i], x = x_pos[i] + summary_widths[i] * 0.49, y = y_top - row_h * 0.5,
|
| 1331 |
-
gp = gpar(col = "white", cex = 0.65, fontface = "bold"))
|
| 1332 |
-
|
| 1333 |
-
grid.rect(x = x_pos[i], y = y_top - row_h, width = summary_widths[i] * 0.985, height = row_h,
|
| 1334 |
-
just = c("left", "top"), gp = gpar(fill = "white", col = "black", lwd = 0.4))
|
| 1335 |
-
grid.text(sprintf("%.1f", summary_values[i]),
|
| 1336 |
-
x = x_pos[i] + summary_widths[i] * 0.49, y = y_top - row_h * 1.5,
|
| 1337 |
-
gp = gpar(cex = 0.65))
|
| 1338 |
-
}
|
| 1339 |
-
|
| 1340 |
-
# Pitch Characteristics Table
|
| 1341 |
-
grid.text("Pitch Characteristics", x = 0.5, y = 0.82,
|
| 1342 |
-
gp = gpar(fontface = "bold", cex = 1.2, col = "#006F71"))
|
| 1343 |
-
|
| 1344 |
-
char_headers <- names(pitch_char)
|
| 1345 |
-
char_widths <- c(0.10, rep(0.055, length(char_headers) - 1))
|
| 1346 |
-
|
| 1347 |
-
x_start_char <- 0.5 - sum(char_widths)/2
|
| 1348 |
-
x_pos_char <- c(x_start_char, x_start_char + cumsum(char_widths[-length(char_widths)]))
|
| 1349 |
-
y_top_char <- 0.80
|
| 1350 |
-
row_h_char <- 0.018
|
| 1351 |
-
|
| 1352 |
-
# Draw pitch characteristics table header
|
| 1353 |
-
for (i in seq_along(char_headers)) {
|
| 1354 |
-
grid.rect(x = x_pos_char[i], y = y_top_char, width = char_widths[i] * 0.985, height = row_h_char,
|
| 1355 |
-
just = c("left", "top"), gp = gpar(fill = "#006F71", col = "black", lwd = 0.5))
|
| 1356 |
-
grid.text(char_headers[i], x = x_pos_char[i] + char_widths[i] * 0.49, y = y_top_char - row_h_char * 0.5,
|
| 1357 |
-
gp = gpar(col = "white", cex = 0.60, fontface = "bold"))
|
| 1358 |
-
}
|
| 1359 |
-
|
| 1360 |
-
# Draw pitch characteristics table rows
|
| 1361 |
-
for (r in 1:min(nrow(pitch_char), 6)) {
|
| 1362 |
-
y_row <- y_top_char - r * row_h_char
|
| 1363 |
-
for (i in seq_along(char_headers)) {
|
| 1364 |
-
val <- pitch_char[[i]][r]
|
| 1365 |
-
bg <- if (r %% 2 == 0) "#f7f7f7" else "white"
|
| 1366 |
-
grid.rect(x = x_pos_char[i], y = y_row, width = char_widths[i] * 0.985, height = row_h_char,
|
| 1367 |
-
just = c("left", "top"), gp = gpar(fill = bg, col = "grey80", lwd = 0.3))
|
| 1368 |
-
display_val <- if (is.numeric(val)) sprintf("%.1f", val) else as.character(val)
|
| 1369 |
-
grid.text(display_val, x = x_pos_char[i] + char_widths[i] * 0.49, y = y_row - row_h_char * 0.5,
|
| 1370 |
-
gp = gpar(cex = 0.58))
|
| 1371 |
-
}
|
| 1372 |
-
}
|
| 1373 |
-
|
| 1374 |
-
# Plot Movement (large, left)
|
| 1375 |
-
pushViewport(viewport(x = 0.25, y = 0.57, width = 0.45, height = 0.24, just = c("center", "top")))
|
| 1376 |
-
print(break_plot, newpage = FALSE)
|
| 1377 |
-
popViewport()
|
| 1378 |
-
|
| 1379 |
-
# Plot Velocity Distribution (large, right)
|
| 1380 |
-
pushViewport(viewport(x = 0.75, y = 0.57, width = 0.45, height = 0.24, just = c("center", "top")))
|
| 1381 |
-
print(velo_plot, newpage = FALSE)
|
| 1382 |
-
popViewport()
|
| 1383 |
-
|
| 1384 |
-
# Location plots
|
| 1385 |
-
pushViewport(viewport(x = 0.25, y = 0.28, width = 0.45, height = 0.18, just = c("center", "top")))
|
| 1386 |
-
print(location_lhb, newpage = FALSE)
|
| 1387 |
-
popViewport()
|
| 1388 |
-
|
| 1389 |
-
pushViewport(viewport(x = 0.75, y = 0.28, width = 0.45, height = 0.18, just = c("center", "top")))
|
| 1390 |
-
print(location_rhb, newpage = FALSE)
|
| 1391 |
-
popViewport()
|
| 1392 |
-
|
| 1393 |
-
# Footer
|
| 1394 |
-
grid.text("Data: TrackMan | Report Generated: Coastal Carolina Baseball",
|
| 1395 |
-
x = 0.5, y = 0.03, gp = gpar(cex = 0.8, col = "grey50"))
|
| 1396 |
-
|
| 1397 |
-
invisible(output_file)
|
| 1398 |
-
}
|
| 1399 |
-
|
| 1400 |
create_pitcher_pitch_char <- function(game_data) {
|
| 1401 |
game_data %>%
|
| 1402 |
filter(TaggedPitchType != "Other", !is.na(TaggedPitchType)) %>%
|
|
@@ -2292,61 +1919,69 @@ server <- function(input, output, session) {
|
|
| 2292 |
}
|
| 2293 |
})
|
| 2294 |
|
| 2295 |
-
|
| 2296 |
-
|
| 2297 |
-
|
| 2298 |
-
|
| 2299 |
-
|
| 2300 |
-
|
| 2301 |
-
|
| 2302 |
-
|
| 2303 |
-
|
| 2304 |
-
|
| 2305 |
-
|
| 2306 |
-
|
| 2307 |
-
|
| 2308 |
-
|
| 2309 |
-
|
| 2310 |
-
|
| 2311 |
-
|
| 2312 |
-
|
| 2313 |
-
|
| 2314 |
-
|
| 2315 |
-
|
| 2316 |
-
|
| 2317 |
-
|
| 2318 |
-
|
| 2319 |
-
|
| 2320 |
-
|
| 2321 |
-
|
| 2322 |
-
|
| 2323 |
-
|
| 2324 |
-
|
| 2325 |
-
|
| 2326 |
-
|
| 2327 |
-
|
| 2328 |
-
|
| 2329 |
-
|
| 2330 |
-
|
| 2331 |
-
|
| 2332 |
-
|
| 2333 |
-
|
| 2334 |
-
|
| 2335 |
-
format(parse_game_day(df), "%Y%m%d"),
|
| 2336 |
-
"_Advanced_Pitcher_Report.pdf"))
|
| 2337 |
-
try(create_advanced_pitcher_pdf(df, ply, out), silent = TRUE)
|
| 2338 |
-
if (file.exists(out)) pdfs <- c(pdfs, out)
|
| 2339 |
-
}
|
| 2340 |
-
if (!length(pdfs)) {
|
| 2341 |
-
showNotification("Failed to generate reports", type="error", duration=5)
|
| 2342 |
return(NULL)
|
| 2343 |
}
|
| 2344 |
-
|
| 2345 |
-
|
| 2346 |
-
|
| 2347 |
-
|
| 2348 |
-
|
| 2349 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2350 |
|
| 2351 |
output$status_message <- renderUI({
|
| 2352 |
if (input$report_type == "hitter") {
|
|
|
|
|
|
|
| 1 |
library(shiny)
|
| 2 |
library(dplyr)
|
| 3 |
library(ggplot2)
|
|
|
|
| 1024 |
)
|
| 1025 |
}
|
| 1026 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1027 |
create_pitcher_pitch_char <- function(game_data) {
|
| 1028 |
game_data %>%
|
| 1029 |
filter(TaggedPitchType != "Other", !is.na(TaggedPitchType)) %>%
|
|
|
|
| 1919 |
}
|
| 1920 |
})
|
| 1921 |
|
| 1922 |
+
output$download_pitcher <- downloadHandler(
|
| 1923 |
+
filename = function() {
|
| 1924 |
+
df <- data_pitcher(); req(df, input$pitcher_name)
|
| 1925 |
+
pitcher_clean <- gsub(" ", "_", input$pitcher_name)
|
| 1926 |
+
date_str <- format(parse_game_day(df %>% filter(Pitcher == input$pitcher_name)), "%Y%m%d")
|
| 1927 |
+
paste0(pitcher_clean, "_", date_str, "_Pitcher_Report.pdf")
|
| 1928 |
+
},
|
| 1929 |
+
content = function(file) {
|
| 1930 |
+
df <- data_pitcher(); req(df, input$pitcher_name)
|
| 1931 |
+
pitch_colors <- c("Fastball"="#FA8072","Four-Seam"="#FA8072","Sinker"="#fdae61",
|
| 1932 |
+
"Slider"="#A020F0","Sweeper"="magenta","Curveball"="#2c7bb6",
|
| 1933 |
+
"ChangeUp"="#90EE90","Splitter"="#90EE32","Cutter"="red")
|
| 1934 |
+
withProgress(message='Generating Pitcher PDF', value=0, {
|
| 1935 |
+
incProgress(.3, detail="Processing data...")
|
| 1936 |
+
incProgress(.4, detail="Creating visualizations...")
|
| 1937 |
+
create_pitcher_pdf(df, input$pitcher_name, file, pitch_colors)
|
| 1938 |
+
incProgress(.3, detail="Finalizing report...")
|
| 1939 |
+
})
|
| 1940 |
+
showNotification("✅ Pitcher report generated!", type="message", duration=3)
|
| 1941 |
+
},
|
| 1942 |
+
contentType = "application/pdf"
|
| 1943 |
+
)
|
| 1944 |
+
|
| 1945 |
+
# ---- Pitcher bulk ZIP
|
| 1946 |
+
output$download_all_coastal_pitchers <- downloadHandler(
|
| 1947 |
+
filename = function() {
|
| 1948 |
+
df <- data_pitcher(); req(df)
|
| 1949 |
+
paste0("Coastal_Pitcher_Reports_", format(parse_game_day(df), "%Y%m%d"), ".zip")
|
| 1950 |
+
},
|
| 1951 |
+
content = function(file) {
|
| 1952 |
+
df <- data_pitcher(); req(df)
|
| 1953 |
+
pitch_colors <- c(
|
| 1954 |
+
"Fastball"="#FA8072","Four-Seam"="#FA8072","Sinker"="#fdae61",
|
| 1955 |
+
"Slider"="#A020F0","Sweeper"="magenta","Curveball"="#2c7bb6",
|
| 1956 |
+
"ChangeUp"="#90EE90","Splitter"="#90EE32","Cutter"="red"
|
| 1957 |
+
)
|
| 1958 |
+
pitchers <- df %>% dplyr::filter(PitcherTeam == "COA_CHA") %>%
|
| 1959 |
+
dplyr::pull(Pitcher) %>% unique() %>% na.omit() %>% sort()
|
| 1960 |
+
if (!length(pitchers)) {
|
| 1961 |
+
showNotification("No Coastal pitchers found", type="error", duration=5)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1962 |
return(NULL)
|
| 1963 |
}
|
| 1964 |
+
withProgress(message='Generating Coastal Pitcher Reports', value=0, {
|
| 1965 |
+
tmp <- tempdir(); pdfs <- character(0); total <- length(pitchers)
|
| 1966 |
+
for (i in seq_along(pitchers)) {
|
| 1967 |
+
ply <- pitchers[i]; incProgress(1/total, detail=paste("Report for", ply))
|
| 1968 |
+
out <- file.path(tmp, paste0(gsub(" ","_",ply), "_",
|
| 1969 |
+
format(parse_game_day(df), "%Y%m%d"),
|
| 1970 |
+
"_Pitcher_Report.pdf"))
|
| 1971 |
+
try(create_pitcher_pdf(df, ply, out, pitch_colors), silent = TRUE)
|
| 1972 |
+
if (file.exists(out)) pdfs <- c(pdfs, out)
|
| 1973 |
+
}
|
| 1974 |
+
if (!length(pdfs)) {
|
| 1975 |
+
showNotification("Failed to generate reports", type="error", duration=5)
|
| 1976 |
+
return(NULL)
|
| 1977 |
+
}
|
| 1978 |
+
zip::zip(zipfile=file, files=basename(pdfs), root=tmp); unlink(pdfs)
|
| 1979 |
+
})
|
| 1980 |
+
showNotification("✅ Coastal pitcher ZIP ready!", type="message", duration=5)
|
| 1981 |
+
},
|
| 1982 |
+
contentType = "application/zip"
|
| 1983 |
+
)
|
| 1984 |
+
|
| 1985 |
|
| 1986 |
output$status_message <- renderUI({
|
| 1987 |
if (input$report_type == "hitter") {
|