Spaces:
Running
Running
Update app.R
Browse files
app.R
CHANGED
|
@@ -128,6 +128,35 @@ app_css <- "
|
|
| 128 |
font-weight: bold;
|
| 129 |
color: #333;
|
| 130 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
"
|
| 132 |
|
| 133 |
# =====================================================================
|
|
@@ -723,26 +752,88 @@ create_report_contact_chart <- function(game_data, player_name) {
|
|
| 723 |
}
|
| 724 |
|
| 725 |
calculate_leaderboards <- function(df, team_meta_df = team_meta) {
|
| 726 |
-
|
| 727 |
-
|
|
|
|
| 728 |
if (is.na(name)) return(name)
|
| 729 |
stringr::str_replace(name, "^\\s*(\\w+)\\s*,\\s*(\\w+)\\s*$", "\\2 \\1")
|
| 730 |
}
|
| 731 |
-
|
| 732 |
get_logo <- function(team_abbr) {
|
| 733 |
if (is.null(team_meta_df) || is.null(team_abbr) || is.na(team_abbr)) return("")
|
|
|
|
| 734 |
logo <- team_meta_df$BTeamLogo[team_meta_df$team_abbr == team_abbr]
|
| 735 |
if (length(logo) == 0 || is.na(logo[1])) return("")
|
| 736 |
logo[1]
|
| 737 |
}
|
| 738 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 739 |
top_ev <- df %>%
|
| 740 |
filter(!is.na(ExitSpeed), !is.na(Batter)) %>%
|
| 741 |
group_by(Batter, BatterTeam) %>%
|
| 742 |
summarise(MaxEV = max(ExitSpeed, na.rm = TRUE), .groups = "drop") %>%
|
| 743 |
arrange(desc(MaxEV)) %>%
|
| 744 |
head(5) %>%
|
| 745 |
-
mutate(
|
|
|
|
| 746 |
|
| 747 |
top_dist <- df %>%
|
| 748 |
filter(!is.na(Distance), !is.na(Batter), Distance > 0) %>%
|
|
@@ -750,7 +841,8 @@ calculate_leaderboards <- function(df, team_meta_df = team_meta) {
|
|
| 750 |
summarise(MaxDist = max(Distance, na.rm = TRUE), .groups = "drop") %>%
|
| 751 |
arrange(desc(MaxDist)) %>%
|
| 752 |
head(5) %>%
|
| 753 |
-
mutate(
|
|
|
|
| 754 |
|
| 755 |
top_velo <- df %>%
|
| 756 |
filter(!is.na(RelSpeed), !is.na(Pitcher)) %>%
|
|
@@ -758,7 +850,8 @@ calculate_leaderboards <- function(df, team_meta_df = team_meta) {
|
|
| 758 |
summarise(MaxVelo = max(RelSpeed, na.rm = TRUE), .groups = "drop") %>%
|
| 759 |
arrange(desc(MaxVelo)) %>%
|
| 760 |
head(5) %>%
|
| 761 |
-
mutate(
|
|
|
|
| 762 |
|
| 763 |
top_whiffs <- df %>%
|
| 764 |
filter(!is.na(Pitcher)) %>%
|
|
@@ -766,9 +859,16 @@ calculate_leaderboards <- function(df, team_meta_df = team_meta) {
|
|
| 766 |
summarise(Whiffs = sum(PitchCall == "StrikeSwinging", na.rm = TRUE), .groups = "drop") %>%
|
| 767 |
arrange(desc(Whiffs)) %>%
|
| 768 |
head(5) %>%
|
| 769 |
-
mutate(
|
|
|
|
| 770 |
|
| 771 |
-
list(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 772 |
}
|
| 773 |
|
| 774 |
create_simple_header <- function(player_name, game_date, bio_data = NULL) {
|
|
@@ -3610,8 +3710,15 @@ output$leaderboard_ui <- renderUI({
|
|
| 3610 |
if (is.null(df)) return(NULL)
|
| 3611 |
|
| 3612 |
leaders <- calculate_leaderboards(df)
|
|
|
|
| 3613 |
|
| 3614 |
make_column <- function(title, data, name_col, value_col, unit) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3615 |
rows <- lapply(seq_len(nrow(data)), function(i) {
|
| 3616 |
logo_html <- if (nzchar(data$Logo[i])) {
|
| 3617 |
tags$img(src = data$Logo[i], class = "leaderboard-logo",
|
|
@@ -3628,7 +3735,27 @@ output$leaderboard_ui <- renderUI({
|
|
| 3628 |
}
|
| 3629 |
|
| 3630 |
div(class = "leaderboard-section",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3631 |
h3(class = "leaderboard-title", icon("trophy"), " Game Leaders"),
|
|
|
|
| 3632 |
div(class = "leaderboard-grid",
|
| 3633 |
make_column("Top Exit Velocity", leaders$exit_velo, "Batter", "MaxEV", "MPH"),
|
| 3634 |
make_column("Top Distances", leaders$distance, "Batter", "MaxDist", "Ft."),
|
|
|
|
| 128 |
font-weight: bold;
|
| 129 |
color: #333;
|
| 130 |
}
|
| 131 |
+
.game-info-bar {
|
| 132 |
+
display: flex;
|
| 133 |
+
justify-content: space-between;
|
| 134 |
+
align-items: center;
|
| 135 |
+
background: linear-gradient(135deg, #006F71 0%, #00a8a8 100%);
|
| 136 |
+
color: white;
|
| 137 |
+
padding: 12px 20px;
|
| 138 |
+
border-radius: 8px;
|
| 139 |
+
margin-bottom: 15px;
|
| 140 |
+
font-size: 0.95em;
|
| 141 |
+
}
|
| 142 |
+
.game-info-item {
|
| 143 |
+
display: flex;
|
| 144 |
+
flex-direction: column;
|
| 145 |
+
align-items: center;
|
| 146 |
+
}
|
| 147 |
+
.game-info-label {
|
| 148 |
+
font-size: 0.75em;
|
| 149 |
+
opacity: 0.85;
|
| 150 |
+
text-transform: uppercase;
|
| 151 |
+
}
|
| 152 |
+
.game-info-value {
|
| 153 |
+
font-weight: bold;
|
| 154 |
+
font-size: 1.1em;
|
| 155 |
+
}
|
| 156 |
+
.game-score {
|
| 157 |
+
font-size: 1.2em;
|
| 158 |
+
font-weight: bold;
|
| 159 |
+
}
|
| 160 |
"
|
| 161 |
|
| 162 |
# =====================================================================
|
|
|
|
| 752 |
}
|
| 753 |
|
| 754 |
calculate_leaderboards <- function(df, team_meta_df = team_meta) {
|
| 755 |
+
|
| 756 |
+
# Helper to format names from "Last, First" to "First Last"
|
| 757 |
+
format_name <- function(name) {
|
| 758 |
if (is.na(name)) return(name)
|
| 759 |
stringr::str_replace(name, "^\\s*(\\w+)\\s*,\\s*(\\w+)\\s*$", "\\2 \\1")
|
| 760 |
}
|
| 761 |
+
|
| 762 |
get_logo <- function(team_abbr) {
|
| 763 |
if (is.null(team_meta_df) || is.null(team_abbr) || is.na(team_abbr)) return("")
|
| 764 |
+
# Match BatterTeam/PitcherTeam to team_abbr in TMB
|
| 765 |
logo <- team_meta_df$BTeamLogo[team_meta_df$team_abbr == team_abbr]
|
| 766 |
if (length(logo) == 0 || is.na(logo[1])) return("")
|
| 767 |
logo[1]
|
| 768 |
}
|
| 769 |
|
| 770 |
+
# Game Info
|
| 771 |
+
stadium <- if ("Stadium" %in% names(df)) unique(na.omit(df$Stadium))[1] else "Unknown"
|
| 772 |
+
level <- if ("Level" %in% names(df)) unique(na.omit(df$Level))[1] else ""
|
| 773 |
+
|
| 774 |
+
league <- if ("League" %in% names(df)) unique(na.omit(df$League))[1] else ""
|
| 775 |
+
|
| 776 |
+
# Format date to MM/DD/YYYY
|
| 777 |
+
game_date <- if ("Date" %in% names(df)) {
|
| 778 |
+
raw_date <- unique(na.omit(df$Date))[1]
|
| 779 |
+
parsed <- tryCatch({
|
| 780 |
+
if (grepl("^\\d{4}-\\d{1,2}-\\d{1,2}", raw_date)) {
|
| 781 |
+
as.Date(raw_date, format = "%Y-%m-%d")
|
| 782 |
+
} else if (grepl("^\\d{1,2}/\\d{1,2}/\\d{4}", raw_date)) {
|
| 783 |
+
as.Date(raw_date, format = "%m/%d/%Y")
|
| 784 |
+
} else {
|
| 785 |
+
as.Date(raw_date)
|
| 786 |
+
}
|
| 787 |
+
}, error = function(e) Sys.Date())
|
| 788 |
+
format(parsed, "%m/%d/%Y")
|
| 789 |
+
} else format(Sys.Date(), "%m/%d/%Y")
|
| 790 |
+
|
| 791 |
+
# Calculate final score from RunsScored
|
| 792 |
+
teams <- unique(c(df$BatterTeam, df$PitcherTeam))
|
| 793 |
+
teams <- teams[!is.na(teams)]
|
| 794 |
+
|
| 795 |
+
score_info <- df %>%
|
| 796 |
+
filter(!is.na(RunsScored), RunsScored > 0) %>%
|
| 797 |
+
group_by(BatterTeam) %>%
|
| 798 |
+
summarise(Runs = sum(RunsScored, na.rm = TRUE), .groups = "drop") %>%
|
| 799 |
+
arrange(desc(Runs))
|
| 800 |
+
|
| 801 |
+
if (nrow(score_info) >= 2) {
|
| 802 |
+
team1 <- score_info$BatterTeam[1]
|
| 803 |
+
runs1 <- score_info$Runs[1]
|
| 804 |
+
team2 <- score_info$BatterTeam[2]
|
| 805 |
+
runs2 <- score_info$Runs[2]
|
| 806 |
+
|
| 807 |
+
# Get team names from TMB if available
|
| 808 |
+
get_team_name <- function(abbr) {
|
| 809 |
+
if (is.null(team_meta_df)) return(abbr)
|
| 810 |
+
name <- team_meta_df$BTeamName[team_meta_df$team_abbr == abbr]
|
| 811 |
+
if (length(name) == 0 || is.na(name[1])) return(abbr)
|
| 812 |
+
name[1]
|
| 813 |
+
}
|
| 814 |
+
|
| 815 |
+
final_score <- paste0(get_team_name(team1), " ", runs1, " - ",
|
| 816 |
+
get_team_name(team2), " ", runs2)
|
| 817 |
+
} else {
|
| 818 |
+
final_score <- "Score N/A"
|
| 819 |
+
}
|
| 820 |
+
|
| 821 |
+
game_info <- list(
|
| 822 |
+
stadium = stadium,
|
| 823 |
+
level = level,
|
| 824 |
+
league = league,
|
| 825 |
+
date = game_date,
|
| 826 |
+
final_score = final_score
|
| 827 |
+
)
|
| 828 |
+
|
| 829 |
top_ev <- df %>%
|
| 830 |
filter(!is.na(ExitSpeed), !is.na(Batter)) %>%
|
| 831 |
group_by(Batter, BatterTeam) %>%
|
| 832 |
summarise(MaxEV = max(ExitSpeed, na.rm = TRUE), .groups = "drop") %>%
|
| 833 |
arrange(desc(MaxEV)) %>%
|
| 834 |
head(5) %>%
|
| 835 |
+
mutate(Batter = sapply(Batter, format_name),
|
| 836 |
+
Logo = sapply(BatterTeam, get_logo))
|
| 837 |
|
| 838 |
top_dist <- df %>%
|
| 839 |
filter(!is.na(Distance), !is.na(Batter), Distance > 0) %>%
|
|
|
|
| 841 |
summarise(MaxDist = max(Distance, na.rm = TRUE), .groups = "drop") %>%
|
| 842 |
arrange(desc(MaxDist)) %>%
|
| 843 |
head(5) %>%
|
| 844 |
+
mutate(Batter = sapply(Batter, format_name),
|
| 845 |
+
Logo = sapply(BatterTeam, get_logo))
|
| 846 |
|
| 847 |
top_velo <- df %>%
|
| 848 |
filter(!is.na(RelSpeed), !is.na(Pitcher)) %>%
|
|
|
|
| 850 |
summarise(MaxVelo = max(RelSpeed, na.rm = TRUE), .groups = "drop") %>%
|
| 851 |
arrange(desc(MaxVelo)) %>%
|
| 852 |
head(5) %>%
|
| 853 |
+
mutate(Pitcher = sapply(Pitcher, format_name),
|
| 854 |
+
Logo = sapply(PitcherTeam, get_logo))
|
| 855 |
|
| 856 |
top_whiffs <- df %>%
|
| 857 |
filter(!is.na(Pitcher)) %>%
|
|
|
|
| 859 |
summarise(Whiffs = sum(PitchCall == "StrikeSwinging", na.rm = TRUE), .groups = "drop") %>%
|
| 860 |
arrange(desc(Whiffs)) %>%
|
| 861 |
head(5) %>%
|
| 862 |
+
mutate(Pitcher = sapply(Pitcher, format_name),
|
| 863 |
+
Logo = sapply(PitcherTeam, get_logo))
|
| 864 |
|
| 865 |
+
list(
|
| 866 |
+
game_info = game_info,
|
| 867 |
+
exit_velo = top_ev,
|
| 868 |
+
distance = top_dist,
|
| 869 |
+
pitch_velo = top_velo,
|
| 870 |
+
whiffs = top_whiffs
|
| 871 |
+
)
|
| 872 |
}
|
| 873 |
|
| 874 |
create_simple_header <- function(player_name, game_date, bio_data = NULL) {
|
|
|
|
| 3710 |
if (is.null(df)) return(NULL)
|
| 3711 |
|
| 3712 |
leaders <- calculate_leaderboards(df)
|
| 3713 |
+
gi <- leaders$game_info
|
| 3714 |
|
| 3715 |
make_column <- function(title, data, name_col, value_col, unit) {
|
| 3716 |
+
if (nrow(data) == 0) {
|
| 3717 |
+
return(div(class = "leaderboard-column",
|
| 3718 |
+
div(class = "leaderboard-column-header", span(title), span(unit)),
|
| 3719 |
+
div("No data available", style = "color: #999; padding: 10px;")))
|
| 3720 |
+
}
|
| 3721 |
+
|
| 3722 |
rows <- lapply(seq_len(nrow(data)), function(i) {
|
| 3723 |
logo_html <- if (nzchar(data$Logo[i])) {
|
| 3724 |
tags$img(src = data$Logo[i], class = "leaderboard-logo",
|
|
|
|
| 3735 |
}
|
| 3736 |
|
| 3737 |
div(class = "leaderboard-section",
|
| 3738 |
+
# Game Info Bar
|
| 3739 |
+
div(class = "game-info-bar",
|
| 3740 |
+
div(class = "game-info-item",
|
| 3741 |
+
span(class = "game-info-label", "Date"),
|
| 3742 |
+
span(class = "game-info-value", gi$date)),
|
| 3743 |
+
div(class = "game-info-item",
|
| 3744 |
+
span(class = "game-info-label", "Stadium"),
|
| 3745 |
+
span(class = "game-info-value", gi$stadium)),
|
| 3746 |
+
div(class = "game-info-item",
|
| 3747 |
+
span(class = "game-info-label", "Level"),
|
| 3748 |
+
span(class = "game-info-value", gi$level)),
|
| 3749 |
+
div(class = "game-info-item",
|
| 3750 |
+
span(class = "game-info-label", "League"),
|
| 3751 |
+
span(class = "game-info-value", gi$league)),
|
| 3752 |
+
div(class = "game-info-item",
|
| 3753 |
+
span(class = "game-info-label", "Final Score"),
|
| 3754 |
+
span(class = "game-score", gi$final_score))
|
| 3755 |
+
),
|
| 3756 |
+
# Leaders Title
|
| 3757 |
h3(class = "leaderboard-title", icon("trophy"), " Game Leaders"),
|
| 3758 |
+
# Leaders Grid
|
| 3759 |
div(class = "leaderboard-grid",
|
| 3760 |
make_column("Top Exit Velocity", leaders$exit_velo, "Batter", "MaxEV", "MPH"),
|
| 3761 |
make_column("Top Distances", leaders$distance, "Batter", "MaxDist", "Ft."),
|