sql_daily / app.py
nesticot's picture
Update app.py
9d0dc08 verified
import polars as pl
import numpy as np
import pandas as pd
import api_scraper
scrape = api_scraper.MLB_Scrape()
from functions import df_update
from functions import pitch_summary_functions
update = df_update.df_update()
from stuff_model import feature_engineering as fe
from stuff_model import stuff_apply
import requests
import joblib
from matplotlib.gridspec import GridSpec
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import datetime
from matplotlib.patches import Rectangle
import cairosvg
from PIL import Image
from io import BytesIO
print(1)
colour_palette = ['#FFB000','#648FFF','#785EF0',
'#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
import pandas as pd
# List of MLB teams and their corresponding ESPN logo URLs
mlb_teams = [
{"team": "AZ", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/ari.png&h=500&w=500"},
{"team": "ATL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/atl.png&h=500&w=500"},
{"team": "BAL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/bal.png&h=500&w=500"},
{"team": "BOS", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/bos.png&h=500&w=500"},
{"team": "CHC", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/chc.png&h=500&w=500"},
{"team": "CWS", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/chw.png&h=500&w=500"},
{"team": "CIN", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/cin.png&h=500&w=500"},
{"team": "CLE", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/cle.png&h=500&w=500"},
{"team": "COL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/col.png&h=500&w=500"},
{"team": "DET", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/det.png&h=500&w=500"},
{"team": "HOU", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/hou.png&h=500&w=500"},
{"team": "KC", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/kc.png&h=500&w=500"},
{"team": "LAA", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/laa.png&h=500&w=500"},
{"team": "LAD", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/lad.png&h=500&w=500"},
{"team": "MIA", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/mia.png&h=500&w=500"},
{"team": "MIL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/mil.png&h=500&w=500"},
{"team": "MIN", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/min.png&h=500&w=500"},
{"team": "NYM", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/nym.png&h=500&w=500"},
{"team": "NYY", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/nyy.png&h=500&w=500"},
{"team": "ATH", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/oak.png&h=500&w=500"},
{"team": "PHI", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/phi.png&h=500&w=500"},
{"team": "PIT", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/pit.png&h=500&w=500"},
{"team": "SD", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/sd.png&h=500&w=500"},
{"team": "SF", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/sf.png&h=500&w=500"},
{"team": "SEA", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/sea.png&h=500&w=500"},
{"team": "STL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/stl.png&h=500&w=500"},
{"team": "TB", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/tb.png&h=500&w=500"},
{"team": "TEX", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/tex.png&h=500&w=500"},
{"team": "TOR", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/tor.png&h=500&w=500"},
{"team": "WSH", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/wsh.png&h=500&w=500"}
]
# Create a DataFrame from the list of dictionaries
df_image = pd.DataFrame(mlb_teams)
image_dict = df_image.set_index('team')['logo_url'].to_dict()
team_list = [x for x in image_dict]
# print(df_image)
scrape = api_scraper.MLB_Scrape()
mlb_team_colors_abb = {
"AZ": "#A71930",
"ATL": "#13274F",
"BAL": "#DF4601",
"BOS": "#BD3039",
"CHC": "#0E3386",
"CHW": "#27251F",
"CIN": "#C6011F",
"CLE": "#0C2340",
"COL": "#33006F",
"DET": "#0C2340",
"HOU": "#002D62",
"KC": "#004687",
"LAA": "#BA0021",
"LAD": "#005A9C",
"MIA": "#00A3E0",
"MIL": "#12284b",
"MIN": "#002B5C",
"NYM": "#002D72",
"NYY": "#142448",
"ATH": "#003831",
"OAK": "#EFB21E",
"PHI": "#E81828",
"PIT": "#FDB827",
"SD": "#2F241D",
"SF": "#FD5A1E",
"SEA": "#0C2C56",
"STL": "#C41E3A",
"TB": "#092C5C",
"TEX": "#003278",
"TOR": "#134A8E",
"WSH": "#AB0003",
"MLB": "#002D72"
}
mlb_team_secondary_colors_abb = {
"AZ": "#3EC1CD",
"ATL": "#CE1141",
"BAL": "#000000",
"BOS": "#0D2B56",
"CHC": "#CC3433",
"CHW": "#C4CED4",
"CIN": "#000000",
"CLE": "#E31937",
"COL": "#C4CED4",
"DET": "#FA4616",
"HOU": "#EB6E1F",
"KC": "#BD9B60",
"LAA": "#003263",
"LAD": "#EF3E42",
"MIA": "#EF3340",
"MIL": "#ffc52f",
"MIN": "#D31145",
"NYM": "#FF5910",
"NYY": "#C4CED4",
"ATH": "#EFB21E",
"OAK": "#EFB21E",
"PHI": "#002D72",
"PIT": "#27251F",
"SD": "#FFC425",
"SF": "#27251F",
"SEA": "#005C5C",
"STL": "#FEDB00",
"TB": "#8FBCE6",
"TEX": "#C0111F",
"TOR": "#E8291C",
"WSH": "#14225A",
"MLB": "#D50032"
}
level_dict = {'1':'MLB',
'11':'AAA',
'12':'AA',
'13':'A+',
'14':'A',
'16':'ROK',
'17':'AFL',
'22':'College',
'21':'Prospects',
'51':'International' }
level_dict_file = {'1':'mlb',
'11':'aaa',
'12':'aa',
'13':'hi_a',
'14':'lo_a',
'16':'rok',
'17':'afl', }
game_type = ['S','R','P','E','A','I','W','F','L']
VALID_PASSWORDS = []
VALID_PASSWORDS.append('')
batter_default = f"""
SELECT
batter_id as id,
batter_name as name,
batter_team as team,
batter_team_id as team_id,
launch_speed as total
FROM my_table
WHERE launch_speed IS NOT NULL
ORDER BY total DESC
LIMIT 10
"""
pitcher_default = f"""
SELECT
pitcher_id AS id,
pitcher_name AS name,
pitcher_team AS team,
pitcher_team_id as team_id,
MAX(start_speed) AS total
FROM my_table
WHERE start_speed > 0
GROUP BY pitcher_id, pitcher_name, pitcher_team,pitcher_team_id
ORDER BY total DESC
LIMIT 10
"""
from shiny import App, reactive, ui, render
from shiny.ui import h2, tags
# Define the login UI
login_ui = ui.page_fluid(
ui.card(
ui.input_password("password", "Enter Patreon Email (or Password from Link):", width="25%"),
ui.tags.input(
type="checkbox",
id="authenticated",
value=False,
disabled=True
),
ui.input_action_button("login", "Login", class_="btn-primary"),
ui.output_text("login_message"),
)
)
# Define the UI layout for the app
main_ui = ui.page_sidebar(
ui.sidebar(
# Row for selecting season and level
ui.row(
ui.column(12, ui.input_date('date_input', 'Select Date')),
),
ui.row(
ui.column(6, ui.input_select('level_input', 'Select Level', level_dict)),
ui.column(6, ui.input_select('type_input', 'Select Type', game_type,selected=game_type, multiple=True,selectize=True)),
),
ui.row(
ui.output_ui("sql_update","Enter SQL Query:"),
),
ui.row(
ui.column(12, ui.input_text('format_string', 'Text Format', value='.1f')),
),
ui.row(
ui.column(12, ui.input_text('title_input', 'Title','Top Exit Velocity')),
),
ui.row(
ui.column(12, ui.input_text('stat_label', 'Stat Label','MPH')),
),
ui.row(ui.input_action_button("generate_plot", "Generate Plot", class_="btn-primary")),
width='800px'),
# Main content area with tabs (placed directly in page_sidebar)
ui.navset_tab(
ui.nav_panel("Batter",
ui.output_plot('plot_batter', width='1500px', height='1500px')
),
ui.nav_panel("Pitcher",
ui.output_plot('plot_pitcher', width='1500px', height='1500px')
),
ui.nav_panel("Batter All",
ui.output_plot('plot_batter_all', width='1500px', height='1500px')
),
ui.nav_panel("Pitcher All",
ui.output_plot('plot_pitcher_all', width='1500px', height='1500px')
),
id='tabset',
)
)
# Combined UI with conditional panel
app_ui = ui.page_fluid(
ui.tags.head(
ui.tags.script(src="script.js")
),
ui.panel_conditional(
"!input.authenticated",
login_ui
),
ui.panel_conditional(
"input.authenticated",
main_ui
)
)
def server(input, output, session):
@reactive.Effect
@reactive.event(input.login)
def check_password():
if input.password() in VALID_PASSWORDS:
ui.update_checkbox("authenticated", value=True)
ui.update_text("login_message", value="")
else:
ui.update_text("login_message", value="Invalid password!")
ui.update_text("password", value="")
@output
@render.text
def login_message():
return ""
@render.ui
@reactive.event(input.tabset, ignore_none=False)
def sql_update():
if input.tabset() == "Batter" or input.tabset() == "Batter All":
return ui.input_text_area(
"query",
"Enter SQL Query:",
value=batter_default,
height="300px",
resize="vertical",
placeholder="Enter your SQL query here...",
spellcheck=False
)
if input.tabset() == "Pitcher" or input.tabset() == "Pitcher All":
return ui.input_text_area(
"query",
"Enter SQL Query:",
value=pitcher_default,
height="200px",
resize="vertical",
placeholder="Enter your SQL query here...",
spellcheck=False)
@reactive.calc
@reactive.event(input.date_id,input.level_input)
def cached_data():
print('MADE')
df_games = (scrape.get_schedule(year_input=[int(str(input.date_input())[:4])],
sport_id=[int(input.level_input())],
game_type=list(input.type_input())).with_columns(pl.col('date').cast(pl.Utf8)).
filter((pl.col('date') == str(input.date_input())))).with_columns(
(pl.col('away')+' @ '+pl.col('home')).alias('matchup'))
game_list = df_games['game_id'].unique().to_list()
# game_list = game_list_df['game_id'].unique().to_list()
data = scrape.get_data(game_list[:])
try:
df = scrape.get_data_df(data)
return df
except TypeError:
print("NONE")
return None
@output
@render.plot
@reactive.event(input.generate_plot, ignore_none=False)
def plot_batter():
with ui.Progress(min=0, max=1) as p:
from PIL import Image
from io import BytesIO
p.set(message="Generating plot", detail="This may take a while...")
p.set(0.3, "Gathering data...")
df_games = (scrape.get_schedule(year_input=[int(str(input.date_input())[:4])],
sport_id=[int(input.level_input())],
game_type=list(input.type_input())).with_columns(pl.col('date').cast(pl.Utf8)).
filter(pl.col('date') == str(input.date_input()))).with_columns(
(pl.col('away')+' @ '+pl.col('home')).alias('matchup'))
game_list = df_games['game_id'].unique().to_list()
# game_list = game_list_df['game_id'].unique().to_list()
data = scrape.get_data(game_list[:])
df = scrape.get_data_df(data)#.filter(pl.col('batter_team').is_in(team_list))
# Create a SQL context
# print('MADE')
sql_ctx = pl.SQLContext()
# Register the DataFrame in the SQL context
sql_ctx.register("my_table", df)
date_select = input.date_input()
# Run SQL query
format_string = input.format_string()
title = input.title_input()
stat_label = input.stat_label()
# rect_list = [0]
date = str(date_select)
title_full = f'{title}{date}'
sql_query = input.query()
result = sql_ctx.execute(sql_query).collect()
df_pandas = result.to_pandas()
print(df_pandas)
# df_pandas['logo'] = df_pandas[f'team'].map(image_dict)
# df_pandas['logo'] = df_pandas[f'team'].map(image_dict)
df_pandas['logo'] = [f'https://www.mlbstatic.com/team-logos/{int(i)}.svg' for i in df_pandas['team_id']]
# df_pandas['picture'] = [f'https://img.mlbstatic.com/mlb-photos/image/upload/w_180,d_people:generic:headshot:silo:current.png,q_auto:best,f_auto/v1/people/{i}/headshot/silo/current' for i in df_pandas[f'id']]
df_pandas['picture'] = [f'https://img.mlbstatic.com/mlb-photos/image/upload/w_180,d_people:generic:headshot:silo:current.png,q_auto:best,f_auto/v1/people/{i}/headshot/silo/current' for i in df_pandas[f'id']]
p.set(0.6, "Creating plot...")
# Set the number of rows and columns for the subplot grid
num_rows = len(df_pandas)+3 # 10 players + 2 for top and bottom
num_cols = 3 # Three columns: two thin ones on the edges and one main column in the center
# Set the font style to Calibri
plt.rcParams['font.family'] = 'calibri'
# Create a figure
fig = plt.figure(figsize=(25, 25))
# Create a GridSpec object with different widths for the columns
gs = gridspec.GridSpec(num_rows, num_cols, figure=fig, width_ratios=[0.5, 9, 0.5])
# rect_list =[5]
positions = [(i + 2, 1) for i in range(num_rows-3)]
# rect_list =[5]
positions = [(i + 2, 1) for i in range(num_rows-3)]
base_size = 36
threshold = 24
scaling_factor = 1.5 # Adjust this factor as needed
# Iterate over the top 10 players in the sorted dataframe
for i, (_, team_row) in enumerate(df_pandas.head(10).iterrows()):
team = team_row[f'team']
player = team_row[f'name']
logo_url = team_row['picture']
team_url = team_row['logo']
# Determine the position in the grid
row, col = positions[i]
# Create a subplot in the GridSpec layout
ax = fig.add_subplot(gs[row, col])
#ax
# Plot the team logo
img = plt.imread(logo_url)
ax.set_xlim(-1.25, 1.25)
#ax.set_ylim(0, 0.2)
ax.imshow(img, extent=[-1.02, -0.84, 0.05, 0.95],aspect=0.2)
# # Plot the team logo
# img = plt.imread(team_url)
# #ax.set_ylim(0, 0.2)
response = requests.get(team_url)
png_data = cairosvg.svg2png(bytestring=response.content, output_width=400,output_height=400)
# Open the PNG with PIL
img = Image.open(BytesIO(png_data))
# img = img.convert("LA")
ax.imshow(img, extent=[-0.81, -0.63, 0.05, 0.95],aspect=0.2)
ax.axis('off')
# Add the rank number to the left of the logo, italicized
ax.text(0.05, 0.5, f'{i + 1}', transform=ax.transAxes, ha='center', va='center', fontsize=36, style='italic')
adjusted_fontsize = max(20, base_size - max(0, (len(player) - threshold) * scaling_factor))
# # Add the team name and metric value as text with bigger font size, bold the metric
ax.text(0.27, 0.5, f'{player}', transform=ax.transAxes, ha='left', va='center', fontsize=adjusted_fontsize, style='italic')
ax.text(0.90, 0.5, f'{team_row["total"]:{format_string}}', transform=ax.transAxes, ha='center', va='center', fontsize=36, weight='bold')
# ax.add_patch(Rectangle((-1.25, 0), 2.5, 1, fill=True,
# lw=2, zorder=10, alpha=0.075, facecolor='yellow',ec=None))
# if i in rect_list:
# ax.add_patch(Rectangle((-1.1, 0.02), 2.2, 0.96, fill=True,
# lw=2, zorder=10, alpha=0.1, facecolor=mlb_team_colors_abb[team],
# ec=None))
# ax.add_patch(Rectangle((-1.1, 0.02), 2.2, 0.96, fill=False,
# lw=4, zorder=11, alpha=1, facecolor=None,
# ec=mlb_team_secondary_colors_abb[team]))
# Adjust the spacing between subplots to place them on the borders
ax_top = fig.add_subplot(gs[0, :])
ax_bot = fig.add_subplot(gs[-1, :])
ax_left = fig.add_subplot(gs[:, 0])
ax_right = fig.add_subplot(gs[:, -1])
ax_top.axis('off')
ax_bot.axis('off')
ax_left.axis('off')
ax_right.axis('off')
ax_bot.text(s='By: @TJStats', x=0.1, y=0.5, fontsize=24, ha='left')
ax_bot.text(s='Data: MLB', x=0.9, y=0.5, fontsize=24, ha='right')
ax_bot.text(s=f"2025 MLB Season\n{datetime.datetime.today().strftime('%Y-%m-%d')}", x=0.5, y=0.5, fontsize=16, ha='center')
#↑↓
ax_stat_select = fig.add_subplot(gs[1, 1])
ax_stat_select.set_xlim(0, 2.5)
import numpy as np
from PIL import Image
blank_image = np.ones((100, 100, 3), dtype=np.uint8) * 255
ax_stat_select.imshow(blank_image, extent=[-0.76, -0.58, 0.05, 0.95],aspect=0.2)
ax_stat_select.text(0.90*2.5, 0.0, f'{stat_label}', ha='center', va='bottom', fontsize=36, style='italic')
ax_stat_select.axis('off')
# Fine-tune the spacing between subplots
#plt.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.05, wspace=0.1, hspace=0.1)
ax_top.text(s=f'{title_full}', x=0.5, y=0.0, fontsize=40, ha='center', va='bottom', style='italic', weight='bold')
ax_watermark = fig.add_subplot(gs[1:-1,1:-1],zorder=-1)
# Hide axes ticks and labels
ax_watermark.set_xticks([])
ax_watermark.set_yticks([])
ax_watermark.set_frame_on(False) # Optional: Hide border
img = Image.open('tj stats circle-01_new.jpg')
img = img.convert("LA")
# Display the image
ax_watermark.imshow(img, extent=[0, 1, 0, 1], origin='upper',zorder=-1, alpha=0.075)
@output
@render.plot
@reactive.event(input.generate_plot, ignore_none=False)
def plot_batter_all():
with ui.Progress(min=0, max=1) as p:
from PIL import Image
from io import BytesIO
p.set(message="Generating plot", detail="This may take a while...")
p.set(0.3, "Gathering data...")
import polars as pl
df_spring = pl.read_parquet(f"hf://datasets/TJStatsApps/mlb_data/data/{level_dict_file[str(input.level_input())]}_pitch_data_2025.parquet")
df_games = (scrape.get_schedule(year_input=[int(str(input.date_input())[:4])],
sport_id=[int(input.level_input())],
game_type=list(input.type_input())).with_columns(pl.col('date').cast(pl.Utf8)).
filter(pl.col('date') == str(input.date_input()))).with_columns(
(pl.col('away')+' @ '+pl.col('home')).alias('matchup'))
game_list = df_games['game_id'].unique().to_list()
# game_list = game_list_df['game_id'].unique().to_list()
data = scrape.get_data(game_list[:])
df = scrape.get_data_df(data)
df = pl.concat([df_spring, df]).unique(subset=['play_id']).sort('game_date', descending=True)
# Create a SQL context
# print('MADE')
sql_ctx = pl.SQLContext()
# Register the DataFrame in the SQL context
sql_ctx.register("my_table", df)
date_select = input.date_input()
# Run SQL query
format_string = input.format_string()
title = input.title_input()
stat_label = input.stat_label()
# rect_list = [0]
date = str(date_select)
title_full = f'{title}'
sql_query = input.query()
result = sql_ctx.execute(sql_query).collect()
df_players = scrape.get_players(sport_id=int(input.level_input()),season=int(str(input.date_input())[:4]))
team_players = dict(zip(df_players['player_id'],df_players['team']))
df_teams = scrape.get_teams()
team_dict = dict(zip(df_teams['team_id'],df_teams['parent_org_id']))
result = result.with_columns(
pl.col('id').replace_strict(team_players, default=None).alias('team')
)
result = result.with_columns(
pl.col('team').replace_strict(team_dict, default=None).alias('team_id')
)
df_pandas = result.to_pandas()
df_pandas['logo'] = [f'https://www.mlbstatic.com/team-logos/{int(i)}.svg' for i in df_pandas['team_id']]
# df_pandas['picture'] = [f'https://img.mlbstatic.com/mlb-photos/image/upload/w_180,d_people:generic:headshot:silo:current.png,q_auto:best,f_auto/v1/people/{i}/headshot/silo/current' for i in df_pandas[f'id']]
df_pandas['picture'] = [f'https://img.mlbstatic.com/mlb-photos/image/upload/w_180,d_people:generic:headshot:silo:current.png,q_auto:best,f_auto/v1/people/{i}/headshot/silo/current' for i in df_pandas[f'id']]
df_pandas = df_pandas.dropna()
p.set(0.6, "Creating plot...")
# Set the number of rows and columns for the subplot grid
num_rows = len(df_pandas)+3 # 10 players + 2 for top and bottom
num_cols = 3 # Three columns: two thin ones on the edges and one main column in the center
# Set the font style to Calibri
plt.rcParams['font.family'] = 'calibri'
# Create a figure
fig = plt.figure(figsize=(25, 25))
# Create a GridSpec object with different widths for the columns
gs = gridspec.GridSpec(num_rows, num_cols, figure=fig, width_ratios=[0.5, 9, 0.5])
# rect_list =[5]
positions = [(i + 2, 1) for i in range(num_rows-3)]
# rect_list =[5]
positions = [(i + 2, 1) for i in range(num_rows-3)]
base_size = 36
threshold = 24
scaling_factor = 1.5 # Adjust this factor as needed
# Iterate over the top 10 players in the sorted dataframe
for i, (_, team_row) in enumerate(df_pandas.head(10).iterrows()):
team = team_row[f'team']
player = team_row[f'name']
logo_url = team_row['picture']
team_url = team_row['logo']
# Determine the position in the grid
row, col = positions[i]
# Create a subplot in the GridSpec layout
ax = fig.add_subplot(gs[row, col])
# Plot the player picture
try:
img = plt.imread(logo_url)
ax.set_xlim(-1.25, 1.25)
ax.imshow(img, extent=[-1.02, -0.84, 0.05, 0.95], aspect=0.2)
except Exception as e:
print(f"Failed to load player image from {logo_url}: {e}")
# Plot the team logo
try:
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
response = requests.get(team_url, headers=headers, timeout=10)
# Validate response
if response.status_code != 200:
print(f"Failed to fetch {team_url}: HTTP {response.status_code}")
raise ValueError(f"HTTP {response.status_code}")
if not response.content:
print(f"Empty response for {team_url}")
raise ValueError("Empty response")
# Check if it's actually SVG or already a raster format
content_type = response.headers.get('content-type', '').lower()
if 'svg' in content_type or team_url.lower().endswith('.svg'):
# Convert SVG to PNG
png_data = cairosvg.svg2png(
bytestring=response.content,
output_width=400,
output_height=400
)
img = Image.open(BytesIO(png_data))
else:
# Already a raster format
img = Image.open(BytesIO(response.content))
ax.imshow(img, extent=[-0.81, -0.63, 0.05, 0.95], aspect=0.2)
except Exception as e:
print(f"Failed to process team logo from {team_url}: {e}")
# Create blank placeholder
blank_image = np.ones((100, 100, 3), dtype=np.uint8) * 240
img = Image.fromarray(blank_image)
ax.imshow(img, extent=[-0.81, -0.63, 0.05, 0.95], aspect=0.2)
ax.axis('off')
# Add the rank number to the left of the logo, italicized
ax.text(0.05, 0.5, f'{i + 1}', transform=ax.transAxes, ha='center', va='center',
fontsize=36, style='italic')
adjusted_fontsize = max(20, base_size - max(0, (len(player) - threshold) * scaling_factor))
# Add the player name
ax.text(0.27, 0.5, f'{player}', transform=ax.transAxes, ha='left', va='center',
fontsize=adjusted_fontsize, style='italic')
# Add the metric value
ax.text(0.90, 0.5, f'{team_row["total"]:{format_string}}', transform=ax.transAxes,
ha='center', va='center', fontsize=36, weight='bold')
# Adjust the spacing between subplots to place them on the borders
ax_top = fig.add_subplot(gs[0, :])
ax_bot = fig.add_subplot(gs[-1, :])
ax_left = fig.add_subplot(gs[:, 0])
ax_right = fig.add_subplot(gs[:, -1])
ax_top.axis('off')
ax_bot.axis('off')
ax_left.axis('off')
ax_right.axis('off')
ax_bot.text(s='By: @TJStats', x=0.1, y=0.5, fontsize=24, ha='left')
ax_bot.text(s='Data: MLB', x=0.9, y=0.5, fontsize=24, ha='right')
ax_bot.text(s=f"2025 MLB Season\n{datetime.datetime.today().strftime('%Y-%m-%d')}",
x=0.5, y=0.5, fontsize=16, ha='center')
ax_stat_select = fig.add_subplot(gs[1, 1])
ax_stat_select.set_xlim(0, 2.5)
blank_image = np.ones((100, 100, 3), dtype=np.uint8) * 255
ax_stat_select.imshow(blank_image, extent=[-0.76, -0.58, 0.05, 0.95], aspect=0.2)
ax_stat_select.text(0.90*2.5, 0.0, f'{stat_label}', ha='center', va='bottom',
fontsize=36, style='italic')
ax_stat_select.axis('off')
ax_top.text(s=f'{title_full}', x=0.5, y=0.0, fontsize=40, ha='center', va='bottom',
style='italic', weight='bold')
ax_watermark = fig.add_subplot(gs[1:-1, 1:-1], zorder=-1)
ax_watermark.set_xticks([])
ax_watermark.set_yticks([])
ax_watermark.set_frame_on(False)
try:
img = Image.open('tj stats circle-01_new.jpg')
img = img.convert("LA")
ax_watermark.imshow(img, extent=[0, 1, 0, 1], origin='upper', zorder=-1, alpha=0.075)
except Exception as e:
print(f"Failed to load watermark image: {e}")
@output
@render.plot
@reactive.event(input.generate_plot, ignore_none=True)
def plot_pitcher():
with ui.Progress(min=0, max=1) as p:
from PIL import Image
from io import BytesIO
p.set(message="Generating plot", detail="This may take a while...")
p.set(0.3, "Gathering data...")
df_games = (scrape.get_schedule(year_input=[int(str(input.date_input())[:4])],
sport_id=[int(input.level_input())],
game_type=list(input.type_input())).with_columns(pl.col('date').cast(pl.Utf8)).
filter(pl.col('date') == str(input.date_input()))).with_columns(
(pl.col('away')+' @ '+pl.col('home')).alias('matchup'))
game_list = df_games['game_id'].unique().to_list()
# game_list = game_list_df['game_id'].unique().to_list()
data = scrape.get_data(game_list[:])
df = scrape.get_data_df(data)
# Create a SQL context
sql_ctx = pl.SQLContext()
# Register the DataFrame in the SQL context
sql_ctx.register("my_table", df)
date_select = input.date_input()
# Run SQL query
format_string = input.format_string()
title = input.title_input()
stat_label = input.stat_label()
# rect_list = [0]
date = str(date_select)
title_full = f'{title}'
sql_query = input.query()
result = sql_ctx.execute(sql_query).collect()
df_pandas = result.to_pandas()
df_pandas['logo'] = [f'https://www.mlbstatic.com/team-logos/{int(i)}.svg' for i in df_pandas['team_id']]
# df_pandas['picture'] = [f'https://img.mlbstatic.com/mlb-photos/image/upload/w_180,d_people:generic:headshot:silo:current.png,q_auto:best,f_auto/v1/people/{i}/headshot/silo/current' for i in df_pandas[f'id']]
df_pandas['picture'] = [f'https://img.mlbstatic.com/mlb-photos/image/upload/w_180,d_people:generic:headshot:silo:current.png,q_auto:best,f_auto/v1/people/{i}/headshot/silo/current' for i in df_pandas[f'id']]
df_pandas = df_pandas.dropna()
p.set(0.6, "Creating plot...")
# Set the number of rows and columns for the subplot grid
num_rows = len(df_pandas)+3 # 10 players + 2 for top and bottom
num_cols = 3 # Three columns: two thin ones on the edges and one main column in the center
# Set the font style to Calibri
plt.rcParams['font.family'] = 'calibri'
# Create a figure
fig = plt.figure(figsize=(25, 25))
# Create a GridSpec object with different widths for the columns
gs = gridspec.GridSpec(num_rows, num_cols, figure=fig, width_ratios=[0.5, 9, 0.5])
# rect_list =[5]
positions = [(i + 2, 1) for i in range(num_rows-3)]
# rect_list =[5]
positions = [(i + 2, 1) for i in range(num_rows-3)]
base_size = 36
threshold = 24
scaling_factor = 1.5 # Adjust this factor as needed
# Iterate over the top 10 players in the sorted dataframe
for i, (_, team_row) in enumerate(df_pandas.head(10).iterrows()):
team = team_row[f'team']
player = team_row[f'name']
logo_url = team_row['picture']
team_url = team_row['logo']
# Determine the position in the grid
row, col = positions[i]
# Create a subplot in the GridSpec layout
ax = fig.add_subplot(gs[row, col])
#ax
# Plot the team logo
img = plt.imread(logo_url)
ax.set_xlim(-1.25, 1.25)
#ax.set_ylim(0, 0.2)
ax.imshow(img, extent=[-1.02, -0.84, 0.05, 0.95],aspect=0.2)
# # Plot the team logo
# img = plt.imread(team_url)
# #ax.set_ylim(0, 0.2)
response = requests.get(team_url)
png_data = cairosvg.svg2png(bytestring=response.content, output_width=400,output_height=400)
# Open the PNG with PIL
img = Image.open(BytesIO(png_data))
# img = img.convert("LA")
ax.imshow(img, extent=[-0.81, -0.63, 0.05, 0.95],aspect=0.2)
ax.axis('off')
# Add the rank number to the left of the logo, italicized
ax.text(0.05, 0.5, f'{i + 1}', transform=ax.transAxes, ha='center', va='center', fontsize=36, style='italic')
adjusted_fontsize = max(20, base_size - max(0, (len(player) - threshold) * scaling_factor))
# # Add the team name and metric value as text with bigger font size, bold the metric
ax.text(0.27, 0.5, f'{player}', transform=ax.transAxes, ha='left', va='center', fontsize=adjusted_fontsize, style='italic')
ax.text(0.90, 0.5, f'{team_row["total"]:{format_string}}', transform=ax.transAxes, ha='center', va='center', fontsize=36, weight='bold')
# ax.add_patch(Rectangle((-1.25, 0), 2.5, 1, fill=True,
# lw=2, zorder=10, alpha=0.075, facecolor='yellow',ec=None))
# if i in rect_list:
# ax.add_patch(Rectangle((-1.1, 0.02), 2.2, 0.96, fill=True,
# lw=2, zorder=10, alpha=0.1, facecolor=mlb_team_colors_abb[team],
# ec=None))
# ax.add_patch(Rectangle((-1.1, 0.02), 2.2, 0.96, fill=False,
# lw=4, zorder=11, alpha=1, facecolor=None,
# ec=mlb_team_secondary_colors_abb[team]))
# Adjust the spacing between subplots to place them on the borders
ax_top = fig.add_subplot(gs[0, :])
ax_bot = fig.add_subplot(gs[-1, :])
ax_left = fig.add_subplot(gs[:, 0])
ax_right = fig.add_subplot(gs[:, -1])
ax_top.axis('off')
ax_bot.axis('off')
ax_left.axis('off')
ax_right.axis('off')
ax_bot.text(s='By: @TJStats', x=0.1, y=0.5, fontsize=24, ha='left')
ax_bot.text(s='Data: MLB', x=0.9, y=0.5, fontsize=24, ha='right')
ax_bot.text(s=f"2025 MLB Season\n{datetime.datetime.today().strftime('%Y-%m-%d')}", x=0.5, y=0.5, fontsize=16, ha='center')
#↑↓
ax_stat_select = fig.add_subplot(gs[1, 1])
ax_stat_select.set_xlim(0, 2.5)
import numpy as np
from PIL import Image
blank_image = np.ones((100, 100, 3), dtype=np.uint8) * 255
ax_stat_select.imshow(blank_image, extent=[-0.76, -0.58, 0.05, 0.95],aspect=0.2)
ax_stat_select.text(0.90*2.5, 0.0, f'{stat_label}', ha='center', va='bottom', fontsize=36, style='italic')
ax_stat_select.axis('off')
# Fine-tune the spacing between subplots
#plt.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.05, wspace=0.1, hspace=0.1)
ax_top.text(s=f'{title_full}', x=0.5, y=0.0, fontsize=40, ha='center', va='bottom', style='italic', weight='bold')
ax_watermark = fig.add_subplot(gs[1:-1,1:-1],zorder=-1)
# Hide axes ticks and labels
ax_watermark.set_xticks([])
ax_watermark.set_yticks([])
ax_watermark.set_frame_on(False) # Optional: Hide border
img = Image.open('tj stats circle-01_new.jpg')
img = img.convert("LA")
# Display the image
ax_watermark.imshow(img, extent=[0, 1, 0, 1], origin='upper',zorder=-1, alpha=0.075)
@output
@render.plot
@reactive.event(input.generate_plot, ignore_none=True)
def plot_pitcher_all():
with ui.Progress(min=0, max=1) as p:
from PIL import Image
from io import BytesIO
p.set(message="Generating plot", detail="This may take a while...")
p.set(0.3, "Gathering data...")
import polars as pl
df_spring = pl.read_parquet(f"hf://datasets/TJStatsApps/mlb_data/data/{level_dict_file[str(input.level_input())]}_pitch_data_2025.parquet")
df_games = (scrape.get_schedule(year_input=[int(str(input.date_input())[:4])],
sport_id=[int(input.level_input())],
game_type=list(input.type_input())).with_columns(pl.col('date').cast(pl.Utf8)).
filter(pl.col('date') == str(input.date_input()))).with_columns(
(pl.col('away')+' @ '+pl.col('home')).alias('matchup'))
game_list = df_games['game_id'].unique().to_list()
# game_list = game_list_df['game_id'].unique().to_list()
data = scrape.get_data(game_list[:])
df = scrape.get_data_df(data)
df = pl.concat([df_spring, df]).unique(subset=['play_id']).sort('game_date', descending=True)
# Create a SQL context
# Create a SQL context
sql_ctx = pl.SQLContext()
# Register the DataFrame in the SQL context
sql_ctx.register("my_table", df)
date_select = input.date_input()
# Run SQL query
format_string = input.format_string()
title = input.title_input()
stat_label = input.stat_label()
# rect_list = [0]
date = str(date_select)
title_full = f'{title}'
sql_query = input.query()
result = sql_ctx.execute(sql_query).collect()
df_pandas = result.to_pandas()
df_pandas['logo'] = [f'https://www.mlbstatic.com/team-logos/{int(i)}.svg' for i in df_pandas['team_id']]
# df_pandas['picture'] = [f'https://img.mlbstatic.com/mlb-photos/image/upload/w_180,d_people:generic:headshot:silo:current.png,q_auto:best,f_auto/v1/people/{i}/headshot/silo/current' for i in df_pandas[f'id']]
df_pandas['picture'] = [f'https://img.mlbstatic.com/mlb-photos/image/upload/w_180,d_people:generic:headshot:silo:current.png,q_auto:best,f_auto/v1/people/{i}/headshot/silo/current' for i in df_pandas[f'id']]
df_pandas = df_pandas.dropna()
p.set(0.6, "Creating plot...")
# Set the number of rows and columns for the subplot grid
num_rows = len(df_pandas)+3 # 10 players + 2 for top and bottom
num_cols = 3 # Three columns: two thin ones on the edges and one main column in the center
# Set the font style to Calibri
plt.rcParams['font.family'] = 'calibri'
# Create a figure
fig = plt.figure(figsize=(25, 25))
# Create a GridSpec object with different widths for the columns
gs = gridspec.GridSpec(num_rows, num_cols, figure=fig, width_ratios=[0.5, 9, 0.5])
# rect_list =[5]
positions = [(i + 2, 1) for i in range(num_rows-3)]
# rect_list =[5]
positions = [(i + 2, 1) for i in range(num_rows-3)]
base_size = 36
threshold = 24
scaling_factor = 1.5 # Adjust this factor as needed
# Iterate over the top 10 players in the sorted dataframe
for i, (_, team_row) in enumerate(df_pandas.head(10).iterrows()):
team = team_row[f'team']
player = team_row[f'name']
logo_url = team_row['picture']
team_url = team_row['logo']
# Determine the position in the grid
row, col = positions[i]
# Create a subplot in the GridSpec layout
ax = fig.add_subplot(gs[row, col])
#ax
# Plot the team logo
img = plt.imread(logo_url)
ax.set_xlim(-1.25, 1.25)
#ax.set_ylim(0, 0.2)
ax.imshow(img, extent=[-1.02, -0.84, 0.05, 0.95],aspect=0.2)
# # Plot the team logo
# img = plt.imread(team_url)
# #ax.set_ylim(0, 0.2)
response = requests.get(team_url)
png_data = cairosvg.svg2png(bytestring=response.content, output_width=400,output_height=400)
# Open the PNG with PIL
img = Image.open(BytesIO(png_data))
# img = img.convert("LA")
ax.imshow(img, extent=[-0.81, -0.63, 0.05, 0.95],aspect=0.2)
ax.axis('off')
# Add the rank number to the left of the logo, italicized
ax.text(0.05, 0.5, f'{i + 1}', transform=ax.transAxes, ha='center', va='center', fontsize=36, style='italic')
adjusted_fontsize = max(20, base_size - max(0, (len(player) - threshold) * scaling_factor))
# # Add the team name and metric value as text with bigger font size, bold the metric
ax.text(0.27, 0.5, f'{player}', transform=ax.transAxes, ha='left', va='center', fontsize=adjusted_fontsize, style='italic')
ax.text(0.90, 0.5, f'{team_row["total"]:{format_string}}', transform=ax.transAxes, ha='center', va='center', fontsize=36, weight='bold')
# ax.add_patch(Rectangle((-1.25, 0), 2.5, 1, fill=True,
# lw=2, zorder=10, alpha=0.075, facecolor='yellow',ec=None))
# if i in rect_list:
# ax.add_patch(Rectangle((-1.1, 0.02), 2.2, 0.96, fill=True,
# lw=2, zorder=10, alpha=0.1, facecolor=mlb_team_colors_abb[team],
# ec=None))
# ax.add_patch(Rectangle((-1.1, 0.02), 2.2, 0.96, fill=False,
# lw=4, zorder=11, alpha=1, facecolor=None,
# ec=mlb_team_secondary_colors_abb[team]))
# Adjust the spacing between subplots to place them on the borders
ax_top = fig.add_subplot(gs[0, :])
ax_bot = fig.add_subplot(gs[-1, :])
ax_left = fig.add_subplot(gs[:, 0])
ax_right = fig.add_subplot(gs[:, -1])
ax_top.axis('off')
ax_bot.axis('off')
ax_left.axis('off')
ax_right.axis('off')
ax_bot.text(s='By: @TJStats', x=0.1, y=0.5, fontsize=24, ha='left')
ax_bot.text(s='Data: MLB', x=0.9, y=0.5, fontsize=24, ha='right')
ax_bot.text(s=f"2025 MLB Season\n{datetime.datetime.today().strftime('%Y-%m-%d')}", x=0.5, y=0.5, fontsize=16, ha='center')
#↑↓
ax_stat_select = fig.add_subplot(gs[1, 1])
ax_stat_select.set_xlim(0, 2.5)
import numpy as np
from PIL import Image
blank_image = np.ones((100, 100, 3), dtype=np.uint8) * 255
ax_stat_select.imshow(blank_image, extent=[-0.76, -0.58, 0.05, 0.95],aspect=0.2)
ax_stat_select.text(0.90*2.5, 0.0, f'{stat_label}', ha='center', va='bottom', fontsize=36, style='italic')
ax_stat_select.axis('off')
# Fine-tune the spacing between subplots
#plt.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.05, wspace=0.1, hspace=0.1)
ax_top.text(s=f'{title_full}', x=0.5, y=0.0, fontsize=40, ha='center', va='bottom', style='italic', weight='bold')
ax_watermark = fig.add_subplot(gs[1:-1,1:-1],zorder=-1)
# Hide axes ticks and labels
ax_watermark.set_xticks([])
ax_watermark.set_yticks([])
ax_watermark.set_frame_on(False) # Optional: Hide border
img = Image.open('tj stats circle-01_new.jpg')
img = img.convert("LA")
# Display the image
ax_watermark.imshow(img, extent=[0, 1, 0, 1], origin='upper',zorder=-1, alpha=0.075)
app = App(app_ui, server)