Spaces:
Running
Running
Update app.R
Browse files
app.R
CHANGED
|
@@ -1031,11 +1031,12 @@ create_bp_contact_map <- function(batter_name, bp_data) {
|
|
| 1031 |
}
|
| 1032 |
|
| 1033 |
create_bp_contact_side <- function(batter_name, bp_data) {
|
|
|
|
| 1034 |
contact_data <- bp_data %>%
|
| 1035 |
filter(Batter == batter_name, BIPind == 1, !is.na(ExitSpeed),
|
| 1036 |
-
!is.na(
|
| 1037 |
|
| 1038 |
-
# Home plate side illustration (tip toward catcher at
|
| 1039 |
plate_df <- data.frame(
|
| 1040 |
x = c(0, 0.708, 1.417, 1.417, 0.708),
|
| 1041 |
y = c(0.25, 0.35, 0.35, 0.15, 0.15)
|
|
@@ -1061,14 +1062,14 @@ create_bp_contact_side <- function(batter_name, bp_data) {
|
|
| 1061 |
ggplot() +
|
| 1062 |
geom_polygon(data = plate_df, aes(x, y), fill = NA, color = "gray70", linewidth = 0.5) +
|
| 1063 |
coord_fixed(xlim = c(-1, 4), ylim = c(0, 5), expand = FALSE) +
|
| 1064 |
-
labs(x = "Contact
|
| 1065 |
ggtitle(paste("Contact Depth vs Height:", batter_name)) +
|
| 1066 |
annotate("text", x = 1.5, y = 2.5, label = "No contact data available", size = 5, color = "gray50") +
|
| 1067 |
base_theme
|
| 1068 |
)
|
| 1069 |
}
|
| 1070 |
|
| 1071 |
-
ggplot(contact_data, aes(x =
|
| 1072 |
geom_polygon(data = plate_df, aes(x, y), fill = "white", color = "black",
|
| 1073 |
linewidth = 0.6, inherit.aes = FALSE) +
|
| 1074 |
geom_point(aes(fill = ExitSpeed), size = 3, shape = 21, color = "black", stroke = 0.4, alpha = 0.85) +
|
|
@@ -1076,7 +1077,7 @@ create_bp_contact_side <- function(batter_name, bp_data) {
|
|
| 1076 |
midpoint = mean(contact_data$ExitSpeed, na.rm = TRUE),
|
| 1077 |
name = "Exit Velo") +
|
| 1078 |
coord_fixed(xlim = c(-1, 4), ylim = c(0, 5), expand = FALSE) +
|
| 1079 |
-
labs(x = "Contact
|
| 1080 |
ggtitle(paste("Contact Depth vs Height:", batter_name)) +
|
| 1081 |
base_theme
|
| 1082 |
}
|
|
@@ -1117,6 +1118,76 @@ bp_leaderboard_colors <- function(x, target = NULL) {
|
|
| 1117 |
cols
|
| 1118 |
}
|
| 1119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1120 |
create_bp_pdf <- function(bp_data, batter_name, output_file) {
|
| 1121 |
if (length(dev.list()) > 0) try(dev.off(), silent = TRUE)
|
| 1122 |
|
|
@@ -7087,6 +7158,36 @@ server <- function(input, output, session) {
|
|
| 7087 |
contentType = "application/zip"
|
| 7088 |
)
|
| 7089 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7090 |
output$download_all_coastal_pitchers <- downloadHandler(
|
| 7091 |
filename = function() {
|
| 7092 |
df <- data_pitcher(); req(df)
|
|
@@ -7387,6 +7488,8 @@ server <- function(input, output, session) {
|
|
| 7387 |
choices = c(date_choices, "All Dates"),
|
| 7388 |
selected = date_choices[1], width = "220px"),
|
| 7389 |
DT::dataTableOutput("bp_leaderboard"),
|
|
|
|
|
|
|
| 7390 |
br(),
|
| 7391 |
h4("BP Spray Chart", style = "color: #006F71;"),
|
| 7392 |
plotOutput("preview_bp_spray", height = "400px"),
|
|
@@ -7441,7 +7544,8 @@ server <- function(input, output, session) {
|
|
| 7441 |
|
| 7442 |
output$bp_leaderboard <- DT::renderDataTable({
|
| 7443 |
lb <- bp_leaderboard_data()
|
| 7444 |
-
|
|
|
|
| 7445 |
|
| 7446 |
# Red = bad, white = group average, green = good (Avg LA scored by closeness to 20 deg)
|
| 7447 |
# BBE stays uncolored - volume, not quality
|
|
|
|
| 1031 |
}
|
| 1032 |
|
| 1033 |
create_bp_contact_side <- function(batter_name, bp_data) {
|
| 1034 |
+
# TrackMan contact coords: X = depth (toward pitcher), Y = height, Z = side
|
| 1035 |
contact_data <- bp_data %>%
|
| 1036 |
filter(Batter == batter_name, BIPind == 1, !is.na(ExitSpeed),
|
| 1037 |
+
!is.na(ContactPositionX), !is.na(ContactPositionY))
|
| 1038 |
|
| 1039 |
+
# Home plate side illustration (tip toward catcher at depth 0, front edge at 1.417 ft)
|
| 1040 |
plate_df <- data.frame(
|
| 1041 |
x = c(0, 0.708, 1.417, 1.417, 0.708),
|
| 1042 |
y = c(0.25, 0.35, 0.35, 0.15, 0.15)
|
|
|
|
| 1062 |
ggplot() +
|
| 1063 |
geom_polygon(data = plate_df, aes(x, y), fill = NA, color = "gray70", linewidth = 0.5) +
|
| 1064 |
coord_fixed(xlim = c(-1, 4), ylim = c(0, 5), expand = FALSE) +
|
| 1065 |
+
labs(x = "Contact Depth (ft in front of plate)", y = "Contact Height (ft)") +
|
| 1066 |
ggtitle(paste("Contact Depth vs Height:", batter_name)) +
|
| 1067 |
annotate("text", x = 1.5, y = 2.5, label = "No contact data available", size = 5, color = "gray50") +
|
| 1068 |
base_theme
|
| 1069 |
)
|
| 1070 |
}
|
| 1071 |
|
| 1072 |
+
ggplot(contact_data, aes(x = ContactPositionX, y = ContactPositionY)) +
|
| 1073 |
geom_polygon(data = plate_df, aes(x, y), fill = "white", color = "black",
|
| 1074 |
linewidth = 0.6, inherit.aes = FALSE) +
|
| 1075 |
geom_point(aes(fill = ExitSpeed), size = 3, shape = 21, color = "black", stroke = 0.4, alpha = 0.85) +
|
|
|
|
| 1077 |
midpoint = mean(contact_data$ExitSpeed, na.rm = TRUE),
|
| 1078 |
name = "Exit Velo") +
|
| 1079 |
coord_fixed(xlim = c(-1, 4), ylim = c(0, 5), expand = FALSE) +
|
| 1080 |
+
labs(x = "Contact Depth (ft in front of plate)", y = "Contact Height (ft)") +
|
| 1081 |
ggtitle(paste("Contact Depth vs Height:", batter_name)) +
|
| 1082 |
base_theme
|
| 1083 |
}
|
|
|
|
| 1118 |
cols
|
| 1119 |
}
|
| 1120 |
|
| 1121 |
+
create_bp_leaderboard_pdf <- function(lb, date_label, output_file) {
|
| 1122 |
+
if (length(dev.list()) > 0) try(dev.off(), silent = TRUE)
|
| 1123 |
+
|
| 1124 |
+
stat_cols <- c("BBE", "Avg EV", "Avg LA", "Max EV", "SC%", "10-30%", "HH%", "Barrel%")
|
| 1125 |
+
color_cols <- setdiff(stat_cols, "BBE") # BBE = volume, not quality
|
| 1126 |
+
|
| 1127 |
+
cell_fill <- matrix("white", nrow = nrow(lb), ncol = length(stat_cols),
|
| 1128 |
+
dimnames = list(NULL, stat_cols))
|
| 1129 |
+
for (cn in color_cols) {
|
| 1130 |
+
cell_fill[, cn] <- if (cn == "Avg LA") bp_leaderboard_colors(lb[[cn]], target = 20)
|
| 1131 |
+
else bp_leaderboard_colors(lb[[cn]])
|
| 1132 |
+
}
|
| 1133 |
+
|
| 1134 |
+
pdf(output_file, width = 11, height = 8.5)
|
| 1135 |
+
on.exit(try(dev.off(), silent = TRUE), add = TRUE)
|
| 1136 |
+
|
| 1137 |
+
name_w <- 0.20
|
| 1138 |
+
col_w <- 0.082
|
| 1139 |
+
row_h <- 0.028
|
| 1140 |
+
x0 <- 0.5 - (name_w + length(stat_cols) * col_w) / 2
|
| 1141 |
+
rows_per_page <- 25
|
| 1142 |
+
n_pages <- max(1, ceiling(nrow(lb) / rows_per_page))
|
| 1143 |
+
|
| 1144 |
+
draw_cell <- function(x, y, w, fill, label, bold = FALSE, txt_col = "black") {
|
| 1145 |
+
grid::grid.rect(x = x, y = y, width = w * 0.99, height = row_h,
|
| 1146 |
+
just = c("left", "top"),
|
| 1147 |
+
gp = grid::gpar(fill = fill, col = "black", lwd = 0.4))
|
| 1148 |
+
grid::grid.text(label, x = x + w * 0.5, y = y - row_h / 2,
|
| 1149 |
+
gp = grid::gpar(cex = 0.85, col = txt_col,
|
| 1150 |
+
fontface = if (bold) "bold" else "plain"))
|
| 1151 |
+
}
|
| 1152 |
+
|
| 1153 |
+
for (pg in seq_len(n_pages)) {
|
| 1154 |
+
grid::grid.newpage()
|
| 1155 |
+
grid::grid.text("Daily BP Leaderboard", y = 0.955,
|
| 1156 |
+
gp = grid::gpar(fontface = "bold", cex = 1.5, col = "#006F71"))
|
| 1157 |
+
if (!is.null(date_label)) {
|
| 1158 |
+
grid::grid.text(date_label, y = 0.915, gp = grid::gpar(cex = 1.1, col = "grey30"))
|
| 1159 |
+
}
|
| 1160 |
+
if (n_pages > 1) {
|
| 1161 |
+
grid::grid.text(paste0("Page ", pg, " of ", n_pages), x = 0.95, y = 0.03,
|
| 1162 |
+
gp = grid::gpar(cex = 0.7, col = "grey50"))
|
| 1163 |
+
}
|
| 1164 |
+
|
| 1165 |
+
y_top <- 0.86
|
| 1166 |
+
|
| 1167 |
+
# Header row
|
| 1168 |
+
draw_cell(x0, y_top, name_w, "#006F71", "Player", bold = TRUE, txt_col = "white")
|
| 1169 |
+
for (j in seq_along(stat_cols)) {
|
| 1170 |
+
draw_cell(x0 + name_w + (j - 1) * col_w, y_top, col_w, "#006F71",
|
| 1171 |
+
stat_cols[j], bold = TRUE, txt_col = "white")
|
| 1172 |
+
}
|
| 1173 |
+
|
| 1174 |
+
idx <- (((pg - 1) * rows_per_page) + 1):min(pg * rows_per_page, nrow(lb))
|
| 1175 |
+
for (r in seq_along(idx)) {
|
| 1176 |
+
i <- idx[r]
|
| 1177 |
+
y_r <- y_top - r * row_h
|
| 1178 |
+
draw_cell(x0, y_r, name_w, "white", lb$Batter[i], bold = TRUE)
|
| 1179 |
+
for (j in seq_along(stat_cols)) {
|
| 1180 |
+
val <- lb[[stat_cols[j]]][i]
|
| 1181 |
+
draw_cell(x0 + name_w + (j - 1) * col_w, y_r, col_w,
|
| 1182 |
+
cell_fill[i, stat_cols[j]],
|
| 1183 |
+
ifelse(is.finite(val), as.character(val), "-"))
|
| 1184 |
+
}
|
| 1185 |
+
}
|
| 1186 |
+
}
|
| 1187 |
+
|
| 1188 |
+
invisible(output_file)
|
| 1189 |
+
}
|
| 1190 |
+
|
| 1191 |
create_bp_pdf <- function(bp_data, batter_name, output_file) {
|
| 1192 |
if (length(dev.list()) > 0) try(dev.off(), silent = TRUE)
|
| 1193 |
|
|
|
|
| 7158 |
contentType = "application/zip"
|
| 7159 |
)
|
| 7160 |
|
| 7161 |
+
output$download_bp_leaderboard <- downloadHandler(
|
| 7162 |
+
filename = function() {
|
| 7163 |
+
sel <- input$bp_leaderboard_date
|
| 7164 |
+
date_str <- if (!is.null(sel) && sel != "All Dates") {
|
| 7165 |
+
gsub("-", "", sel)
|
| 7166 |
+
} else if (is.null(sel)) {
|
| 7167 |
+
df <- data_bp(); req(df)
|
| 7168 |
+
tryCatch(format(parse_game_day(df), "%Y%m%d"),
|
| 7169 |
+
error = function(e) format(Sys.Date(), "%Y%m%d"))
|
| 7170 |
+
} else "All_Dates"
|
| 7171 |
+
paste0("BP_Leaderboard_", date_str, ".pdf")
|
| 7172 |
+
},
|
| 7173 |
+
content = function(file) {
|
| 7174 |
+
lb <- bp_leaderboard_data()
|
| 7175 |
+
req(nrow(lb) > 0)
|
| 7176 |
+
sel <- input$bp_leaderboard_date
|
| 7177 |
+
date_label <- if (!is.null(sel) && sel != "All Dates") {
|
| 7178 |
+
format(as.Date(sel), "%B %e, %Y")
|
| 7179 |
+
} else if (is.null(sel)) {
|
| 7180 |
+
df <- data_bp()
|
| 7181 |
+
tryCatch(format(parse_game_day(df), "%B %e, %Y"), error = function(e) NULL)
|
| 7182 |
+
} else "All Dates"
|
| 7183 |
+
withProgress(message='Generating Leaderboard PDF', value=0.5, {
|
| 7184 |
+
create_bp_leaderboard_pdf(lb, date_label, file)
|
| 7185 |
+
})
|
| 7186 |
+
showNotification("Leaderboard PDF ready!", type="message", duration=3)
|
| 7187 |
+
},
|
| 7188 |
+
contentType = "application/pdf"
|
| 7189 |
+
)
|
| 7190 |
+
|
| 7191 |
output$download_all_coastal_pitchers <- downloadHandler(
|
| 7192 |
filename = function() {
|
| 7193 |
df <- data_pitcher(); req(df)
|
|
|
|
| 7488 |
choices = c(date_choices, "All Dates"),
|
| 7489 |
selected = date_choices[1], width = "220px"),
|
| 7490 |
DT::dataTableOutput("bp_leaderboard"),
|
| 7491 |
+
div(style = "margin-top:8px;",
|
| 7492 |
+
downloadButton("download_bp_leaderboard", "Download Leaderboard PDF", class = "btn-secondary")),
|
| 7493 |
br(),
|
| 7494 |
h4("BP Spray Chart", style = "color: #006F71;"),
|
| 7495 |
plotOutput("preview_bp_spray", height = "400px"),
|
|
|
|
| 7544 |
|
| 7545 |
output$bp_leaderboard <- DT::renderDataTable({
|
| 7546 |
lb <- bp_leaderboard_data()
|
| 7547 |
+
# jsonlite (loaded after shiny) masks shiny::validate, so namespace explicitly
|
| 7548 |
+
shiny::validate(shiny::need(nrow(lb) > 0, "No BP data for this date"))
|
| 7549 |
|
| 7550 |
# Red = bad, white = group average, green = good (Avg LA scored by closeness to 20 deg)
|
| 7551 |
# BBE stays uncolored - volume, not quality
|