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)