|
|
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 |
|
|
|
|
|
|
|
|
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"} |
|
|
] |
|
|
|
|
|
|
|
|
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] |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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"), |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
main_ui = ui.page_sidebar( |
|
|
ui.sidebar( |
|
|
|
|
|
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'), |
|
|
|
|
|
|
|
|
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', |
|
|
|
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
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() |
|
|
|
|
|
data = scrape.get_data(game_list[:]) |
|
|
df = scrape.get_data_df(data) |
|
|
|
|
|
|
|
|
sql_ctx = pl.SQLContext() |
|
|
|
|
|
sql_ctx.register("my_table", df) |
|
|
|
|
|
date_select = input.date_input() |
|
|
|
|
|
format_string = input.format_string() |
|
|
title = input.title_input() |
|
|
stat_label = input.stat_label() |
|
|
|
|
|
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'] = [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']] |
|
|
|
|
|
p.set(0.6, "Creating plot...") |
|
|
|
|
|
|
|
|
num_rows = len(df_pandas)+3 |
|
|
num_cols = 3 |
|
|
|
|
|
|
|
|
plt.rcParams['font.family'] = 'calibri' |
|
|
|
|
|
|
|
|
fig = plt.figure(figsize=(25, 25)) |
|
|
|
|
|
|
|
|
gs = gridspec.GridSpec(num_rows, num_cols, figure=fig, width_ratios=[0.5, 9, 0.5]) |
|
|
|
|
|
|
|
|
positions = [(i + 2, 1) for i in range(num_rows-3)] |
|
|
|
|
|
positions = [(i + 2, 1) for i in range(num_rows-3)] |
|
|
base_size = 36 |
|
|
threshold = 24 |
|
|
scaling_factor = 1.5 |
|
|
|
|
|
|
|
|
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'] |
|
|
|
|
|
|
|
|
row, col = positions[i] |
|
|
|
|
|
|
|
|
ax = fig.add_subplot(gs[row, col]) |
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
response = requests.get(team_url) |
|
|
|
|
|
png_data = cairosvg.svg2png(bytestring=response.content, output_width=400,output_height=400) |
|
|
|
|
|
|
|
|
img = Image.open(BytesIO(png_data)) |
|
|
|
|
|
|
|
|
ax.imshow(img, extent=[-0.81, -0.63, 0.05, 0.95],aspect=0.2) |
|
|
ax.axis('off') |
|
|
|
|
|
|
|
|
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)) |
|
|
|
|
|
|
|
|
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_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') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
@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() |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
sql_ctx = pl.SQLContext() |
|
|
|
|
|
sql_ctx.register("my_table", df) |
|
|
|
|
|
date_select = input.date_input() |
|
|
|
|
|
format_string = input.format_string() |
|
|
title = input.title_input() |
|
|
stat_label = input.stat_label() |
|
|
|
|
|
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 = df_pandas.dropna() |
|
|
p.set(0.6, "Creating plot...") |
|
|
|
|
|
|
|
|
num_rows = len(df_pandas)+3 |
|
|
num_cols = 3 |
|
|
|
|
|
|
|
|
plt.rcParams['font.family'] = 'calibri' |
|
|
|
|
|
|
|
|
fig = plt.figure(figsize=(25, 25)) |
|
|
|
|
|
|
|
|
gs = gridspec.GridSpec(num_rows, num_cols, figure=fig, width_ratios=[0.5, 9, 0.5]) |
|
|
|
|
|
|
|
|
positions = [(i + 2, 1) for i in range(num_rows-3)] |
|
|
|
|
|
positions = [(i + 2, 1) for i in range(num_rows-3)] |
|
|
base_size = 36 |
|
|
threshold = 24 |
|
|
scaling_factor = 1.5 |
|
|
|
|
|
|
|
|
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'] |
|
|
|
|
|
|
|
|
row, col = positions[i] |
|
|
|
|
|
|
|
|
ax = fig.add_subplot(gs[row, col]) |
|
|
|
|
|
|
|
|
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}") |
|
|
|
|
|
|
|
|
try: |
|
|
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'} |
|
|
response = requests.get(team_url, headers=headers, timeout=10) |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
content_type = response.headers.get('content-type', '').lower() |
|
|
|
|
|
if 'svg' in content_type or team_url.lower().endswith('.svg'): |
|
|
|
|
|
png_data = cairosvg.svg2png( |
|
|
bytestring=response.content, |
|
|
output_width=400, |
|
|
output_height=400 |
|
|
) |
|
|
img = Image.open(BytesIO(png_data)) |
|
|
else: |
|
|
|
|
|
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}") |
|
|
|
|
|
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') |
|
|
|
|
|
|
|
|
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)) |
|
|
|
|
|
|
|
|
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_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() |
|
|
|
|
|
data = scrape.get_data(game_list[:]) |
|
|
df = scrape.get_data_df(data) |
|
|
|
|
|
sql_ctx = pl.SQLContext() |
|
|
|
|
|
sql_ctx.register("my_table", df) |
|
|
|
|
|
date_select = input.date_input() |
|
|
|
|
|
format_string = input.format_string() |
|
|
title = input.title_input() |
|
|
stat_label = input.stat_label() |
|
|
|
|
|
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 = df_pandas.dropna() |
|
|
p.set(0.6, "Creating plot...") |
|
|
|
|
|
|
|
|
num_rows = len(df_pandas)+3 |
|
|
num_cols = 3 |
|
|
|
|
|
|
|
|
plt.rcParams['font.family'] = 'calibri' |
|
|
|
|
|
|
|
|
fig = plt.figure(figsize=(25, 25)) |
|
|
|
|
|
|
|
|
gs = gridspec.GridSpec(num_rows, num_cols, figure=fig, width_ratios=[0.5, 9, 0.5]) |
|
|
|
|
|
|
|
|
positions = [(i + 2, 1) for i in range(num_rows-3)] |
|
|
|
|
|
positions = [(i + 2, 1) for i in range(num_rows-3)] |
|
|
base_size = 36 |
|
|
threshold = 24 |
|
|
scaling_factor = 1.5 |
|
|
|
|
|
|
|
|
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'] |
|
|
|
|
|
|
|
|
row, col = positions[i] |
|
|
|
|
|
|
|
|
ax = fig.add_subplot(gs[row, col]) |
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
response = requests.get(team_url) |
|
|
|
|
|
png_data = cairosvg.svg2png(bytestring=response.content, output_width=400,output_height=400) |
|
|
|
|
|
|
|
|
img = Image.open(BytesIO(png_data)) |
|
|
|
|
|
|
|
|
ax.imshow(img, extent=[-0.81, -0.63, 0.05, 0.95],aspect=0.2) |
|
|
ax.axis('off') |
|
|
|
|
|
|
|
|
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)) |
|
|
|
|
|
|
|
|
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_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') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@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() |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
sql_ctx = pl.SQLContext() |
|
|
|
|
|
sql_ctx.register("my_table", df) |
|
|
|
|
|
date_select = input.date_input() |
|
|
|
|
|
format_string = input.format_string() |
|
|
title = input.title_input() |
|
|
stat_label = input.stat_label() |
|
|
|
|
|
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 = df_pandas.dropna() |
|
|
p.set(0.6, "Creating plot...") |
|
|
|
|
|
|
|
|
num_rows = len(df_pandas)+3 |
|
|
num_cols = 3 |
|
|
|
|
|
|
|
|
plt.rcParams['font.family'] = 'calibri' |
|
|
|
|
|
|
|
|
fig = plt.figure(figsize=(25, 25)) |
|
|
|
|
|
|
|
|
gs = gridspec.GridSpec(num_rows, num_cols, figure=fig, width_ratios=[0.5, 9, 0.5]) |
|
|
|
|
|
|
|
|
positions = [(i + 2, 1) for i in range(num_rows-3)] |
|
|
|
|
|
positions = [(i + 2, 1) for i in range(num_rows-3)] |
|
|
base_size = 36 |
|
|
threshold = 24 |
|
|
scaling_factor = 1.5 |
|
|
|
|
|
|
|
|
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'] |
|
|
|
|
|
|
|
|
row, col = positions[i] |
|
|
|
|
|
|
|
|
ax = fig.add_subplot(gs[row, col]) |
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
response = requests.get(team_url) |
|
|
|
|
|
png_data = cairosvg.svg2png(bytestring=response.content, output_width=400,output_height=400) |
|
|
|
|
|
|
|
|
img = Image.open(BytesIO(png_data)) |
|
|
|
|
|
|
|
|
ax.imshow(img, extent=[-0.81, -0.63, 0.05, 0.95],aspect=0.2) |
|
|
ax.axis('off') |
|
|
|
|
|
|
|
|
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)) |
|
|
|
|
|
|
|
|
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_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') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app = App(app_ui, server) |