Spaces:
Sleeping
Sleeping
Kaveh commited on
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
# =============================================
|
| 3 |
# Gradio App for Chess Game Analysis - Lichess API Version
|
| 4 |
-
#
|
| 5 |
# =============================================
|
| 6 |
|
| 7 |
import gradio as gr
|
|
@@ -32,17 +32,12 @@ TITLES_TO_ANALYZE = ['GM', 'IM', 'FM', 'CM', 'WGM', 'WIM', 'WFM', 'WCM', 'NM']
|
|
| 32 |
# =============================================
|
| 33 |
def categorize_time_control(tc_str, speed_info):
|
| 34 |
"""Categorizes time control based on speed info or parsed string."""
|
| 35 |
-
# 1. Prioritize speed info from API
|
| 36 |
if isinstance(speed_info, str) and speed_info in ['bullet', 'blitz', 'rapid', 'classical', 'correspondence']:
|
| 37 |
return speed_info.capitalize()
|
| 38 |
-
|
| 39 |
-
# 2. Handle invalid or special tc_str inputs
|
| 40 |
if not isinstance(tc_str, str) or tc_str in ['-', '?', 'Unknown']:
|
| 41 |
return 'Unknown'
|
| 42 |
if tc_str == 'Correspondence':
|
| 43 |
return 'Correspondence'
|
| 44 |
-
|
| 45 |
-
# 3. Handle format like "180+2"
|
| 46 |
if '+' in tc_str:
|
| 47 |
parts = tc_str.split('+')
|
| 48 |
if len(parts) == 2:
|
|
@@ -60,8 +55,6 @@ def categorize_time_control(tc_str, speed_info):
|
|
| 60 |
return 'Unknown'
|
| 61 |
else:
|
| 62 |
return 'Unknown'
|
| 63 |
-
|
| 64 |
-
# 4. Handle format like "300" (only base time)
|
| 65 |
else:
|
| 66 |
try:
|
| 67 |
base = int(tc_str)
|
|
@@ -100,12 +93,11 @@ except Exception as e:
|
|
| 100 |
def load_from_lichess_api(username: str, time_period_key: str, perf_type: str, rated: bool, eco_map: dict, progress=None):
|
| 101 |
if not username: return pd.DataFrame(), "⚠️ Enter username."
|
| 102 |
if not perf_type: return pd.DataFrame(), "⚠️ Select game type."
|
| 103 |
-
# Safe handling of progress
|
| 104 |
if progress is not None:
|
| 105 |
try:
|
| 106 |
progress(0, desc="Initializing...")
|
| 107 |
except Exception:
|
| 108 |
-
pass
|
| 109 |
username_lower = username.lower()
|
| 110 |
status_message = f"Fetching {perf_type} games..."
|
| 111 |
if progress is not None:
|
|
@@ -239,7 +231,7 @@ def plot_win_loss_pie(df, display_name):
|
|
| 239 |
fig = px.pie(values=result_counts.values, names=result_counts.index, title=f'Overall Results for {display_name}',
|
| 240 |
color=result_counts.index, color_discrete_map={'Win': '#4CAF50', 'Draw': '#B0BEC5', 'Loss': '#F44336'}, hole=0.3)
|
| 241 |
fig.update_traces(textposition='inside', textinfo='percent+label', pull=[0.05 if x == 'Win' else 0 for x in result_counts.index])
|
| 242 |
-
fig.update_layout(dragmode=False)
|
| 243 |
return fig
|
| 244 |
|
| 245 |
def plot_win_loss_by_color(df):
|
|
@@ -255,7 +247,7 @@ def plot_win_loss_by_color(df):
|
|
| 255 |
fig = px.bar(color_results_pct, barmode='stack', title='Results by Color', labels={'value': '%', 'PlayerColor': 'Played As'},
|
| 256 |
color='PlayerResultString', color_discrete_map={'Win': '#4CAF50', 'Draw': '#B0BEC5', 'Loss': '#F44336'},
|
| 257 |
text_auto='.1f', category_orders={"PlayerColor": ["White", "Black"]})
|
| 258 |
-
fig.update_layout(yaxis_title="Percentage (%)", xaxis_title="Color Played", dragmode=False)
|
| 259 |
fig.update_traces(textangle=0)
|
| 260 |
return fig
|
| 261 |
|
|
@@ -269,7 +261,7 @@ def plot_rating_trend(df, display_name):
|
|
| 269 |
fig.add_trace(go.Scatter(x=df_sorted['Date'], y=df_sorted['PlayerElo'], mode='lines+markers', name='Elo',
|
| 270 |
line=dict(color='#1E88E5', width=2), marker=dict(size=5, opacity=0.7)))
|
| 271 |
fig.update_layout(title=f'{display_name}\'s Rating Trend', xaxis_title='Date', yaxis_title='Elo Rating',
|
| 272 |
-
hovermode="x unified", xaxis_rangeslider_visible=True, dragmode=False)
|
| 273 |
return fig
|
| 274 |
|
| 275 |
def plot_performance_vs_opponent_elo(df):
|
|
@@ -280,7 +272,7 @@ def plot_performance_vs_opponent_elo(df):
|
|
| 280 |
color_discrete_map={'Win': '#4CAF50', 'Draw': '#B0BEC5', 'Loss': '#F44336'}, points='outliers')
|
| 281 |
fig.add_hline(y=0, line_dash="dash", line_color="grey")
|
| 282 |
fig.update_traces(marker=dict(opacity=0.8))
|
| 283 |
-
fig.update_layout(dragmode=False)
|
| 284 |
return fig
|
| 285 |
|
| 286 |
def plot_games_by_dow(df):
|
|
@@ -290,7 +282,7 @@ def plot_games_by_dow(df):
|
|
| 290 |
fig = px.bar(games_by_dow, x=games_by_dow.index, y=games_by_dow.values, title="Games by Day of Week",
|
| 291 |
labels={'x': 'Day', 'y': 'Games'}, text=games_by_dow.values)
|
| 292 |
fig.update_traces(marker_color='#9C27B0', textposition='outside')
|
| 293 |
-
fig.update_layout(dragmode=False)
|
| 294 |
return fig
|
| 295 |
|
| 296 |
def plot_winrate_by_dow(df):
|
|
@@ -303,7 +295,7 @@ def plot_winrate_by_dow(df):
|
|
| 303 |
fig = px.bar(win_rate, x=win_rate.index, y=win_rate.values, title="Win Rate (%) by Day",
|
| 304 |
labels={'x': 'Day', 'y': 'Win Rate (%)'}, text=win_rate.values)
|
| 305 |
fig.update_traces(marker_color='#FF9800', texttemplate='%{text:.1f}%', textposition='outside')
|
| 306 |
-
fig.update_layout(yaxis_range=[0, 100], dragmode=False)
|
| 307 |
return fig
|
| 308 |
|
| 309 |
def plot_games_by_hour(df):
|
|
@@ -312,7 +304,7 @@ def plot_games_by_hour(df):
|
|
| 312 |
fig = px.bar(games_by_hour, x=games_by_hour.index, y=games_by_hour.values, title="Games by Hour (UTC)",
|
| 313 |
labels={'x': 'Hour', 'y': 'Games'}, text=games_by_hour.values)
|
| 314 |
fig.update_traces(marker_color='#03A9F4', textposition='outside')
|
| 315 |
-
fig.update_layout(xaxis=dict(tickmode='linear'), dragmode=False)
|
| 316 |
return fig
|
| 317 |
|
| 318 |
def plot_winrate_by_hour(df):
|
|
@@ -324,7 +316,7 @@ def plot_winrate_by_hour(df):
|
|
| 324 |
fig = px.line(win_rate, x=win_rate.index, y=win_rate.values, markers=True, title="Win Rate (%) by Hour (UTC)",
|
| 325 |
labels={'x': 'Hour', 'y': 'Win Rate (%)'})
|
| 326 |
fig.update_traces(line_color='#8BC34A')
|
| 327 |
-
fig.update_layout(yaxis_range=[0, 100], xaxis=dict(tickmode='linear'), dragmode=False)
|
| 328 |
return fig
|
| 329 |
|
| 330 |
def plot_games_per_year(df):
|
|
@@ -333,7 +325,7 @@ def plot_games_per_year(df):
|
|
| 333 |
fig = px.bar(games_per_year, x=games_per_year.index, y=games_per_year.values, title='Games Per Year',
|
| 334 |
labels={'x': 'Year', 'y': 'Games'}, text=games_per_year.values)
|
| 335 |
fig.update_traces(marker_color='#2196F3', textposition='outside')
|
| 336 |
-
fig.update_layout(xaxis_title="Year", yaxis_title="Number of Games", xaxis={'type': 'category'}, dragmode=False)
|
| 337 |
return fig
|
| 338 |
|
| 339 |
def plot_win_rate_per_year(df):
|
|
@@ -345,7 +337,7 @@ def plot_win_rate_per_year(df):
|
|
| 345 |
fig = px.line(win_rate, x=win_rate.index, y=win_rate.values, title='Win Rate (%) Per Year', markers=True,
|
| 346 |
labels={'x': 'Year', 'y': 'Win Rate (%)'})
|
| 347 |
fig.update_traces(line_color='#FFC107', line_width=2.5)
|
| 348 |
-
fig.update_layout(yaxis_range=[0, 100], dragmode=False)
|
| 349 |
return fig
|
| 350 |
|
| 351 |
def plot_performance_by_time_control(df):
|
|
@@ -363,7 +355,7 @@ def plot_performance_by_time_control(df):
|
|
| 363 |
fig = px.bar(tc_results_pct, title='Performance by Time Control', labels={'value': '%', 'TimeControl_Category': 'Category'},
|
| 364 |
color='PlayerResultString', color_discrete_map={'Win': '#4CAF50', 'Draw': '#B0BEC5', 'Loss': '#F44336'},
|
| 365 |
barmode='group', text_auto='.1f')
|
| 366 |
-
fig.update_layout(xaxis_title="Time Control Category", yaxis_title="Percentage (%)", dragmode=False)
|
| 367 |
fig.update_traces(textangle=0)
|
| 368 |
return fig
|
| 369 |
except Exception:
|
|
@@ -375,7 +367,7 @@ def plot_opening_frequency(df, top_n=20, opening_col='OpeningName_API'):
|
|
| 375 |
opening_counts = df[df[opening_col] != 'Unknown Opening'][opening_col].value_counts().nlargest(top_n)
|
| 376 |
fig = px.bar(opening_counts, y=opening_counts.index, x=opening_counts.values, orientation='h',
|
| 377 |
title=f'Top {top_n} Openings ({source_label})', labels={'y': 'Opening', 'x': 'Games'}, text=opening_counts.values)
|
| 378 |
-
fig.update_layout(yaxis={'categoryorder': 'total ascending'}, dragmode=False)
|
| 379 |
fig.update_traces(marker_color='#673AB7', textposition='outside')
|
| 380 |
return fig
|
| 381 |
|
|
@@ -392,7 +384,7 @@ def plot_win_rate_by_opening(df, min_games=5, top_n=20, opening_col='OpeningName
|
|
| 392 |
title=f'Top {top_n} Openings by Win Rate (Min {min_games} games, {source_label})',
|
| 393 |
labels={'win_rate': 'Win Rate (%)', opening_col: 'Opening'}, text='win_rate')
|
| 394 |
fig.update_traces(texttemplate='%{text:.1f}%', textposition='inside', marker_color='#009688')
|
| 395 |
-
fig.update_layout(yaxis={'categoryorder': 'total ascending'}, xaxis_title="Win Rate (%)", dragmode=False)
|
| 396 |
return fig
|
| 397 |
|
| 398 |
def plot_most_frequent_opponents(df, top_n=20):
|
|
@@ -400,7 +392,7 @@ def plot_most_frequent_opponents(df, top_n=20):
|
|
| 400 |
opp_counts = df[df['OpponentName'] != 'Unknown']['OpponentName'].value_counts().nlargest(top_n)
|
| 401 |
fig = px.bar(opp_counts, y=opp_counts.index, x=opp_counts.values, orientation='h',
|
| 402 |
title=f'Top {top_n} Opponents', labels={'y': 'Opponent', 'x': 'Games'}, text=opp_counts.values)
|
| 403 |
-
fig.update_layout(yaxis={'categoryorder': 'total ascending'}, dragmode=False)
|
| 404 |
fig.update_traces(marker_color='#FF5722', textposition='outside')
|
| 405 |
return fig
|
| 406 |
|
|
@@ -410,7 +402,7 @@ def plot_games_by_dom(df):
|
|
| 410 |
fig = px.bar(games_by_dom, x=games_by_dom.index, y=games_by_dom.values, title="Games Played per Day of Month",
|
| 411 |
labels={'x': 'Day of Month', 'y': 'Number of Games'}, text=games_by_dom.values)
|
| 412 |
fig.update_traces(marker_color='#E91E63', textposition='outside')
|
| 413 |
-
fig.update_layout(xaxis=dict(tickmode='linear'), dragmode=False)
|
| 414 |
return fig
|
| 415 |
|
| 416 |
def plot_winrate_by_dom(df):
|
|
@@ -422,7 +414,7 @@ def plot_winrate_by_dom(df):
|
|
| 422 |
fig = px.line(win_rate, x=win_rate.index, y=win_rate.values, markers=True, title="Win Rate (%) per Day of Month",
|
| 423 |
labels={'x': 'Day of Month', 'y': 'Win Rate (%)'})
|
| 424 |
fig.update_traces(line_color='#FF5722')
|
| 425 |
-
fig.update_layout(yaxis_range=[0, 100], xaxis=dict(tickmode='linear'), dragmode=False)
|
| 426 |
return fig
|
| 427 |
|
| 428 |
def plot_time_forfeit_summary(wins_tf, losses_tf):
|
|
@@ -430,7 +422,7 @@ def plot_time_forfeit_summary(wins_tf, losses_tf):
|
|
| 430 |
df_tf = pd.DataFrame(data)
|
| 431 |
fig = px.bar(df_tf, x='Outcome', y='Count', title="Time Forfeit Summary", color='Outcome',
|
| 432 |
color_discrete_map={'Won on Time': '#4CAF50', 'Lost on Time': '#F44336'}, text='Count')
|
| 433 |
-
fig.update_layout(showlegend=False, dragmode=False)
|
| 434 |
fig.update_traces(textposition='outside')
|
| 435 |
return fig
|
| 436 |
|
|
@@ -439,7 +431,7 @@ def plot_time_forfeit_by_tc(tf_games_df):
|
|
| 439 |
tf_by_tc = tf_games_df['TimeControl_Category'].value_counts()
|
| 440 |
fig = px.bar(tf_by_tc, x=tf_by_tc.index, y=tf_by_tc.values, title="Time Forfeits by Time Control",
|
| 441 |
labels={'x': 'Category', 'y': 'Forfeits'}, text=tf_by_tc.values)
|
| 442 |
-
fig.update_layout(dragmode=False)
|
| 443 |
fig.update_traces(marker_color='#795548', textposition='outside')
|
| 444 |
return fig
|
| 445 |
|
|
@@ -464,9 +456,9 @@ def filter_and_analyze_time_forfeits(df):
|
|
| 464 |
# =============================================
|
| 465 |
def perform_full_analysis(username, time_period_key, perf_type, selected_titles_list, progress=gr.Progress(track_tqdm=True)):
|
| 466 |
df, status_msg = load_from_lichess_api(username, time_period_key, perf_type, DEFAULT_RATED_ONLY, ECO_MAPPING, progress)
|
| 467 |
-
num_outputs =
|
| 468 |
if not isinstance(df, pd.DataFrame) or df.empty:
|
| 469 |
-
return status_msg, pd.DataFrame(), *([None] * (num_outputs -
|
| 470 |
try:
|
| 471 |
fig_pie = plot_win_loss_pie(df, username)
|
| 472 |
fig_color = plot_win_loss_by_color(df)
|
|
@@ -478,7 +470,6 @@ def perform_full_analysis(username, time_period_key, perf_type, selected_titles_
|
|
| 478 |
d = len(df[df['PlayerResultNumeric'] == 0.5])
|
| 479 |
wr = (w / total_g * 100) if total_g > 0 else 0
|
| 480 |
avg_opp = df['OpponentElo'].mean()
|
| 481 |
-
# Fixed formatting issue
|
| 482 |
avg_opp_display = f"{avg_opp:.0f}" if not pd.isna(avg_opp) else 'N/A'
|
| 483 |
overview_stats_md = f"**Total:** {total_g:,} | **WR:** {wr:.1f}% | **W/L/D:** {w}/{l}/{d} | **Avg Opp:** {avg_opp_display}"
|
| 484 |
fig_games_yr = plot_games_per_year(df)
|
|
@@ -504,7 +495,7 @@ def perform_full_analysis(username, time_period_key, perf_type, selected_titles_
|
|
| 504 |
term_counts = df['Termination'].value_counts()
|
| 505 |
fig_term_all = px.bar(term_counts, x=term_counts.index, y=term_counts.values, title="Overall Termination Reasons",
|
| 506 |
labels={'x': 'Reason', 'y': 'Count'}, text=term_counts.values)
|
| 507 |
-
fig_term_all.update_layout(dragmode=False)
|
| 508 |
fig_term_all.update_traces(textposition='outside')
|
| 509 |
titled_status_msg = ""
|
| 510 |
fig_titled_pie, fig_titled_color, fig_titled_rating, df_titled_h2h = go.Figure(), go.Figure(), go.Figure(), pd.DataFrame()
|
|
@@ -525,22 +516,35 @@ def perform_full_analysis(username, time_period_key, perf_type, selected_titles_
|
|
| 525 |
titled_status_msg = f"ℹ️ No games found vs selected titles ({', '.join(selected_titles_list)})."
|
| 526 |
else:
|
| 527 |
titled_status_msg = "ℹ️ Select titles from the sidebar to analyze."
|
| 528 |
-
return (status_msg, df, fig_pie, overview_stats_md, fig_color, fig_rating, fig_elo_diff, fig_games_yr, fig_wr_yr,
|
| 529 |
"(Results by color shown in Overview)", fig_games_dow, fig_wr_dow, fig_games_hod, fig_wr_hod, fig_games_dom,
|
| 530 |
fig_wr_dom, fig_perf_tc, fig_open_freq_api, fig_open_wr_api, fig_open_freq_cust, fig_open_wr_cust,
|
| 531 |
fig_opp_freq, df_opp_list, fig_opp_elo, titled_status_msg, fig_titled_pie, fig_titled_color, fig_titled_rating,
|
| 532 |
df_titled_h2h, fig_tf_summary, fig_tf_tc, df_tf_list, fig_term_all)
|
| 533 |
except Exception as e:
|
| 534 |
error_msg = f"🚨 Error generating results: {e}\n{traceback.format_exc()}"
|
| 535 |
-
return error_msg, pd.DataFrame(), *([None] * (num_outputs -
|
| 536 |
|
| 537 |
# =============================================
|
| 538 |
# Gradio Interface Definition
|
| 539 |
# =============================================
|
| 540 |
-
css = """
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 541 |
with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
|
| 542 |
gr.Markdown("# ♟️ Lichess Insights\nAnalyze rated game statistics from Lichess API.")
|
| 543 |
df_state = gr.State(pd.DataFrame())
|
|
|
|
| 544 |
with gr.Row():
|
| 545 |
with gr.Column(scale=1, min_width=250): # Sidebar
|
| 546 |
gr.Markdown("## ⚙️ Settings")
|
|
@@ -556,70 +560,110 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
|
|
| 556 |
with gr.Column(scale=4): # Main Content
|
| 557 |
with gr.Tabs() as tabs:
|
| 558 |
with gr.TabItem("1. Overview", id=0):
|
| 559 |
-
overview_stats_md_out = gr.Markdown()
|
| 560 |
-
with gr.Row():
|
| 561 |
overview_plot_pie = gr.Plot(label="Overall Results")
|
| 562 |
overview_plot_color = gr.Plot(label="Results by Color")
|
| 563 |
overview_plot_rating = gr.Plot(label="Rating Trend")
|
| 564 |
overview_plot_elo_diff = gr.Plot(label="Elo Advantage vs. Result")
|
| 565 |
with gr.TabItem("2. Perf. Over Time", id=1):
|
| 566 |
-
|
| 567 |
-
|
|
|
|
| 568 |
with gr.TabItem("3. Perf. by Color", id=2):
|
| 569 |
-
color_plot_placeholder = gr.Markdown("(Results by color shown in Overview)")
|
| 570 |
with gr.TabItem("4. Time & Date", id=3):
|
| 571 |
gr.Markdown("### Day of Week")
|
| 572 |
-
with gr.Row():
|
| 573 |
time_plot_games_dow = gr.Plot(label="Games by Day of Week")
|
| 574 |
time_plot_wr_dow = gr.Plot(label="Win Rate by Day of Week")
|
| 575 |
gr.Markdown("### Hour of Day (UTC)")
|
| 576 |
-
with gr.Row():
|
| 577 |
time_plot_games_hod = gr.Plot(label="Games by Hour (UTC)")
|
| 578 |
time_plot_wr_hod = gr.Plot(label="Win Rate by Hour (UTC)")
|
| 579 |
gr.Markdown("### Day of Month")
|
| 580 |
-
with gr.Row():
|
| 581 |
time_plot_games_dom = gr.Plot(label="Games by Day of Month")
|
| 582 |
time_plot_wr_dom = gr.Plot(label="Win Rate by Day of Month")
|
| 583 |
gr.Markdown("### Time Control Category")
|
| 584 |
-
time_plot_perf_tc = gr.Plot(label="Performance by Time Control")
|
| 585 |
with gr.TabItem("5. ECO & Openings", id=4):
|
| 586 |
gr.Markdown("#### API Names")
|
| 587 |
-
eco_plot_freq_api = gr.Plot(label="Opening Frequency (API)")
|
| 588 |
-
eco_plot_wr_api = gr.Plot(label="Opening Win Rate (API)")
|
| 589 |
gr.Markdown("---")
|
| 590 |
gr.Markdown("#### Custom Map")
|
| 591 |
if not ECO_MAPPING:
|
| 592 |
gr.Markdown("⚠️ Custom map not loaded.")
|
| 593 |
-
eco_plot_freq_cust = gr.Plot(label="Opening Frequency (Custom)")
|
| 594 |
-
eco_plot_wr_cust = gr.Plot(label="Opening Win Rate (Custom)")
|
| 595 |
with gr.TabItem("6. Opponents", id=5):
|
| 596 |
-
opp_plot_freq = gr.Plot(label="Frequent Opponents")
|
| 597 |
-
opp_df_list = gr.DataFrame(label="Top Opponents List", wrap=True)
|
| 598 |
-
opp_plot_elo = gr.Plot(label="Elo Advantage vs Result")
|
| 599 |
with gr.TabItem("7. vs Titled", id=6):
|
| 600 |
gr.Markdown("Analysis based on sidebar selection.")
|
| 601 |
-
titled_status = gr.Markdown()
|
| 602 |
-
with gr.Row():
|
| 603 |
titled_plot_pie = gr.Plot(label="Results vs Selected Titles")
|
| 604 |
titled_plot_color = gr.Plot(label="Results by Color vs Selected Titles")
|
| 605 |
titled_plot_rating = gr.Plot(label="Rating Trend vs Selected Titles")
|
| 606 |
-
titled_df_h2h_comp = gr.DataFrame(label="Head-to-Head vs Selected Titles", wrap=True)
|
| 607 |
with gr.TabItem("8. Termination", id=7):
|
| 608 |
gr.Markdown("### Time Forfeit")
|
| 609 |
-
term_plot_tf_summary = gr.Plot(label="Time Forfeit Summary")
|
| 610 |
-
term_plot_tf_tc = gr.Plot(label="Time Forfeits by Time Control")
|
| 611 |
with gr.Accordion("View Recent TF Games", open=False):
|
| 612 |
-
term_df_tf_list = gr.DataFrame(label="Recent TF Games", wrap=True)
|
| 613 |
gr.Markdown("### Overall Termination")
|
| 614 |
-
term_plot_all = gr.Plot(label="Overall Termination")
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 623 |
|
| 624 |
# --- Launch the Gradio App ---
|
| 625 |
if __name__ == "__main__":
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
# =============================================
|
| 3 |
# Gradio App for Chess Game Analysis - Lichess API Version
|
| 4 |
+
# v18: Fixed empty chart visibility and chart sizing for mobile
|
| 5 |
# =============================================
|
| 6 |
|
| 7 |
import gradio as gr
|
|
|
|
| 32 |
# =============================================
|
| 33 |
def categorize_time_control(tc_str, speed_info):
|
| 34 |
"""Categorizes time control based on speed info or parsed string."""
|
|
|
|
| 35 |
if isinstance(speed_info, str) and speed_info in ['bullet', 'blitz', 'rapid', 'classical', 'correspondence']:
|
| 36 |
return speed_info.capitalize()
|
|
|
|
|
|
|
| 37 |
if not isinstance(tc_str, str) or tc_str in ['-', '?', 'Unknown']:
|
| 38 |
return 'Unknown'
|
| 39 |
if tc_str == 'Correspondence':
|
| 40 |
return 'Correspondence'
|
|
|
|
|
|
|
| 41 |
if '+' in tc_str:
|
| 42 |
parts = tc_str.split('+')
|
| 43 |
if len(parts) == 2:
|
|
|
|
| 55 |
return 'Unknown'
|
| 56 |
else:
|
| 57 |
return 'Unknown'
|
|
|
|
|
|
|
| 58 |
else:
|
| 59 |
try:
|
| 60 |
base = int(tc_str)
|
|
|
|
| 93 |
def load_from_lichess_api(username: str, time_period_key: str, perf_type: str, rated: bool, eco_map: dict, progress=None):
|
| 94 |
if not username: return pd.DataFrame(), "⚠️ Enter username."
|
| 95 |
if not perf_type: return pd.DataFrame(), "⚠️ Select game type."
|
|
|
|
| 96 |
if progress is not None:
|
| 97 |
try:
|
| 98 |
progress(0, desc="Initializing...")
|
| 99 |
except Exception:
|
| 100 |
+
pass
|
| 101 |
username_lower = username.lower()
|
| 102 |
status_message = f"Fetching {perf_type} games..."
|
| 103 |
if progress is not None:
|
|
|
|
| 231 |
fig = px.pie(values=result_counts.values, names=result_counts.index, title=f'Overall Results for {display_name}',
|
| 232 |
color=result_counts.index, color_discrete_map={'Win': '#4CAF50', 'Draw': '#B0BEC5', 'Loss': '#F44336'}, hole=0.3)
|
| 233 |
fig.update_traces(textposition='inside', textinfo='percent+label', pull=[0.05 if x == 'Win' else 0 for x in result_counts.index])
|
| 234 |
+
fig.update_layout(dragmode=False, autosize=True, height=400, width=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 235 |
return fig
|
| 236 |
|
| 237 |
def plot_win_loss_by_color(df):
|
|
|
|
| 247 |
fig = px.bar(color_results_pct, barmode='stack', title='Results by Color', labels={'value': '%', 'PlayerColor': 'Played As'},
|
| 248 |
color='PlayerResultString', color_discrete_map={'Win': '#4CAF50', 'Draw': '#B0BEC5', 'Loss': '#F44336'},
|
| 249 |
text_auto='.1f', category_orders={"PlayerColor": ["White", "Black"]})
|
| 250 |
+
fig.update_layout(yaxis_title="Percentage (%)", xaxis_title="Color Played", dragmode=False, autosize=True, height=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 251 |
fig.update_traces(textangle=0)
|
| 252 |
return fig
|
| 253 |
|
|
|
|
| 261 |
fig.add_trace(go.Scatter(x=df_sorted['Date'], y=df_sorted['PlayerElo'], mode='lines+markers', name='Elo',
|
| 262 |
line=dict(color='#1E88E5', width=2), marker=dict(size=5, opacity=0.7)))
|
| 263 |
fig.update_layout(title=f'{display_name}\'s Rating Trend', xaxis_title='Date', yaxis_title='Elo Rating',
|
| 264 |
+
hovermode="x unified", xaxis_rangeslider_visible=True, dragmode=False, autosize=True, height=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 265 |
return fig
|
| 266 |
|
| 267 |
def plot_performance_vs_opponent_elo(df):
|
|
|
|
| 272 |
color_discrete_map={'Win': '#4CAF50', 'Draw': '#B0BEC5', 'Loss': '#F44336'}, points='outliers')
|
| 273 |
fig.add_hline(y=0, line_dash="dash", line_color="grey")
|
| 274 |
fig.update_traces(marker=dict(opacity=0.8))
|
| 275 |
+
fig.update_layout(dragmode=False, autosize=True, height=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 276 |
return fig
|
| 277 |
|
| 278 |
def plot_games_by_dow(df):
|
|
|
|
| 282 |
fig = px.bar(games_by_dow, x=games_by_dow.index, y=games_by_dow.values, title="Games by Day of Week",
|
| 283 |
labels={'x': 'Day', 'y': 'Games'}, text=games_by_dow.values)
|
| 284 |
fig.update_traces(marker_color='#9C27B0', textposition='outside')
|
| 285 |
+
fig.update_layout(dragmode=False, autosize=True, height=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 286 |
return fig
|
| 287 |
|
| 288 |
def plot_winrate_by_dow(df):
|
|
|
|
| 295 |
fig = px.bar(win_rate, x=win_rate.index, y=win_rate.values, title="Win Rate (%) by Day",
|
| 296 |
labels={'x': 'Day', 'y': 'Win Rate (%)'}, text=win_rate.values)
|
| 297 |
fig.update_traces(marker_color='#FF9800', texttemplate='%{text:.1f}%', textposition='outside')
|
| 298 |
+
fig.update_layout(yaxis_range=[0, 100], dragmode=False, autosize=True, height=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 299 |
return fig
|
| 300 |
|
| 301 |
def plot_games_by_hour(df):
|
|
|
|
| 304 |
fig = px.bar(games_by_hour, x=games_by_hour.index, y=games_by_hour.values, title="Games by Hour (UTC)",
|
| 305 |
labels={'x': 'Hour', 'y': 'Games'}, text=games_by_hour.values)
|
| 306 |
fig.update_traces(marker_color='#03A9F4', textposition='outside')
|
| 307 |
+
fig.update_layout(xaxis=dict(tickmode='linear'), dragmode=False, autosize=True, height=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 308 |
return fig
|
| 309 |
|
| 310 |
def plot_winrate_by_hour(df):
|
|
|
|
| 316 |
fig = px.line(win_rate, x=win_rate.index, y=win_rate.values, markers=True, title="Win Rate (%) by Hour (UTC)",
|
| 317 |
labels={'x': 'Hour', 'y': 'Win Rate (%)'})
|
| 318 |
fig.update_traces(line_color='#8BC34A')
|
| 319 |
+
fig.update_layout(yaxis_range=[0, 100], xaxis=dict(tickmode='linear'), dragmode=False, autosize=True, height=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 320 |
return fig
|
| 321 |
|
| 322 |
def plot_games_per_year(df):
|
|
|
|
| 325 |
fig = px.bar(games_per_year, x=games_per_year.index, y=games_per_year.values, title='Games Per Year',
|
| 326 |
labels={'x': 'Year', 'y': 'Games'}, text=games_per_year.values)
|
| 327 |
fig.update_traces(marker_color='#2196F3', textposition='outside')
|
| 328 |
+
fig.update_layout(xaxis_title="Year", yaxis_title="Number of Games", xaxis={'type': 'category'}, dragmode=False, autosize=True, height=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 329 |
return fig
|
| 330 |
|
| 331 |
def plot_win_rate_per_year(df):
|
|
|
|
| 337 |
fig = px.line(win_rate, x=win_rate.index, y=win_rate.values, title='Win Rate (%) Per Year', markers=True,
|
| 338 |
labels={'x': 'Year', 'y': 'Win Rate (%)'})
|
| 339 |
fig.update_traces(line_color='#FFC107', line_width=2.5)
|
| 340 |
+
fig.update_layout(yaxis_range=[0, 100], dragmode=False, autosize=True, height=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 341 |
return fig
|
| 342 |
|
| 343 |
def plot_performance_by_time_control(df):
|
|
|
|
| 355 |
fig = px.bar(tc_results_pct, title='Performance by Time Control', labels={'value': '%', 'TimeControl_Category': 'Category'},
|
| 356 |
color='PlayerResultString', color_discrete_map={'Win': '#4CAF50', 'Draw': '#B0BEC5', 'Loss': '#F44336'},
|
| 357 |
barmode='group', text_auto='.1f')
|
| 358 |
+
fig.update_layout(xaxis_title="Time Control Category", yaxis_title="Percentage (%)", dragmode=False, autosize=True, height=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 359 |
fig.update_traces(textangle=0)
|
| 360 |
return fig
|
| 361 |
except Exception:
|
|
|
|
| 367 |
opening_counts = df[df[opening_col] != 'Unknown Opening'][opening_col].value_counts().nlargest(top_n)
|
| 368 |
fig = px.bar(opening_counts, y=opening_counts.index, x=opening_counts.values, orientation='h',
|
| 369 |
title=f'Top {top_n} Openings ({source_label})', labels={'y': 'Opening', 'x': 'Games'}, text=opening_counts.values)
|
| 370 |
+
fig.update_layout(yaxis={'categoryorder': 'total ascending'}, dragmode=False, autosize=True, height=500, margin=dict(l=20, r=20, t=50, b=20))
|
| 371 |
fig.update_traces(marker_color='#673AB7', textposition='outside')
|
| 372 |
return fig
|
| 373 |
|
|
|
|
| 384 |
title=f'Top {top_n} Openings by Win Rate (Min {min_games} games, {source_label})',
|
| 385 |
labels={'win_rate': 'Win Rate (%)', opening_col: 'Opening'}, text='win_rate')
|
| 386 |
fig.update_traces(texttemplate='%{text:.1f}%', textposition='inside', marker_color='#009688')
|
| 387 |
+
fig.update_layout(yaxis={'categoryorder': 'total ascending'}, xaxis_title="Win Rate (%)", dragmode=False, autosize=True, height=500, margin=dict(l=20, r=20, t=50, b=20))
|
| 388 |
return fig
|
| 389 |
|
| 390 |
def plot_most_frequent_opponents(df, top_n=20):
|
|
|
|
| 392 |
opp_counts = df[df['OpponentName'] != 'Unknown']['OpponentName'].value_counts().nlargest(top_n)
|
| 393 |
fig = px.bar(opp_counts, y=opp_counts.index, x=opp_counts.values, orientation='h',
|
| 394 |
title=f'Top {top_n} Opponents', labels={'y': 'Opponent', 'x': 'Games'}, text=opp_counts.values)
|
| 395 |
+
fig.update_layout(yaxis={'categoryorder': 'total ascending'}, dragmode=False, autosize=True, height=500, margin=dict(l=20, r=20, t=50, b=20))
|
| 396 |
fig.update_traces(marker_color='#FF5722', textposition='outside')
|
| 397 |
return fig
|
| 398 |
|
|
|
|
| 402 |
fig = px.bar(games_by_dom, x=games_by_dom.index, y=games_by_dom.values, title="Games Played per Day of Month",
|
| 403 |
labels={'x': 'Day of Month', 'y': 'Number of Games'}, text=games_by_dom.values)
|
| 404 |
fig.update_traces(marker_color='#E91E63', textposition='outside')
|
| 405 |
+
fig.update_layout(xaxis=dict(tickmode='linear'), dragmode=False, autosize=True, height=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 406 |
return fig
|
| 407 |
|
| 408 |
def plot_winrate_by_dom(df):
|
|
|
|
| 414 |
fig = px.line(win_rate, x=win_rate.index, y=win_rate.values, markers=True, title="Win Rate (%) per Day of Month",
|
| 415 |
labels={'x': 'Day of Month', 'y': 'Win Rate (%)'})
|
| 416 |
fig.update_traces(line_color='#FF5722')
|
| 417 |
+
fig.update_layout(yaxis_range=[0, 100], xaxis=dict(tickmode='linear'), dragmode=False, autosize=True, height=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 418 |
return fig
|
| 419 |
|
| 420 |
def plot_time_forfeit_summary(wins_tf, losses_tf):
|
|
|
|
| 422 |
df_tf = pd.DataFrame(data)
|
| 423 |
fig = px.bar(df_tf, x='Outcome', y='Count', title="Time Forfeit Summary", color='Outcome',
|
| 424 |
color_discrete_map={'Won on Time': '#4CAF50', 'Lost on Time': '#F44336'}, text='Count')
|
| 425 |
+
fig.update_layout(showlegend=False, dragmode=False, autosize=True, height=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 426 |
fig.update_traces(textposition='outside')
|
| 427 |
return fig
|
| 428 |
|
|
|
|
| 431 |
tf_by_tc = tf_games_df['TimeControl_Category'].value_counts()
|
| 432 |
fig = px.bar(tf_by_tc, x=tf_by_tc.index, y=tf_by_tc.values, title="Time Forfeits by Time Control",
|
| 433 |
labels={'x': 'Category', 'y': 'Forfeits'}, text=tf_by_tc.values)
|
| 434 |
+
fig.update_layout(dragmode=False, autosize=True, height=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 435 |
fig.update_traces(marker_color='#795548', textposition='outside')
|
| 436 |
return fig
|
| 437 |
|
|
|
|
| 456 |
# =============================================
|
| 457 |
def perform_full_analysis(username, time_period_key, perf_type, selected_titles_list, progress=gr.Progress(track_tqdm=True)):
|
| 458 |
df, status_msg = load_from_lichess_api(username, time_period_key, perf_type, DEFAULT_RATED_ONLY, ECO_MAPPING, progress)
|
| 459 |
+
num_outputs = 34 # Adjusted for visibility state
|
| 460 |
if not isinstance(df, pd.DataFrame) or df.empty:
|
| 461 |
+
return status_msg, pd.DataFrame(), False, *([None] * (num_outputs - 3))
|
| 462 |
try:
|
| 463 |
fig_pie = plot_win_loss_pie(df, username)
|
| 464 |
fig_color = plot_win_loss_by_color(df)
|
|
|
|
| 470 |
d = len(df[df['PlayerResultNumeric'] == 0.5])
|
| 471 |
wr = (w / total_g * 100) if total_g > 0 else 0
|
| 472 |
avg_opp = df['OpponentElo'].mean()
|
|
|
|
| 473 |
avg_opp_display = f"{avg_opp:.0f}" if not pd.isna(avg_opp) else 'N/A'
|
| 474 |
overview_stats_md = f"**Total:** {total_g:,} | **WR:** {wr:.1f}% | **W/L/D:** {w}/{l}/{d} | **Avg Opp:** {avg_opp_display}"
|
| 475 |
fig_games_yr = plot_games_per_year(df)
|
|
|
|
| 495 |
term_counts = df['Termination'].value_counts()
|
| 496 |
fig_term_all = px.bar(term_counts, x=term_counts.index, y=term_counts.values, title="Overall Termination Reasons",
|
| 497 |
labels={'x': 'Reason', 'y': 'Count'}, text=term_counts.values)
|
| 498 |
+
fig_term_all.update_layout(dragmode=False, autosize=True, height=400, margin=dict(l=20, r=20, t=50, b=20))
|
| 499 |
fig_term_all.update_traces(textposition='outside')
|
| 500 |
titled_status_msg = ""
|
| 501 |
fig_titled_pie, fig_titled_color, fig_titled_rating, df_titled_h2h = go.Figure(), go.Figure(), go.Figure(), pd.DataFrame()
|
|
|
|
| 516 |
titled_status_msg = f"ℹ️ No games found vs selected titles ({', '.join(selected_titles_list)})."
|
| 517 |
else:
|
| 518 |
titled_status_msg = "ℹ️ Select titles from the sidebar to analyze."
|
| 519 |
+
return (status_msg, df, True, fig_pie, overview_stats_md, fig_color, fig_rating, fig_elo_diff, fig_games_yr, fig_wr_yr,
|
| 520 |
"(Results by color shown in Overview)", fig_games_dow, fig_wr_dow, fig_games_hod, fig_wr_hod, fig_games_dom,
|
| 521 |
fig_wr_dom, fig_perf_tc, fig_open_freq_api, fig_open_wr_api, fig_open_freq_cust, fig_open_wr_cust,
|
| 522 |
fig_opp_freq, df_opp_list, fig_opp_elo, titled_status_msg, fig_titled_pie, fig_titled_color, fig_titled_rating,
|
| 523 |
df_titled_h2h, fig_tf_summary, fig_tf_tc, df_tf_list, fig_term_all)
|
| 524 |
except Exception as e:
|
| 525 |
error_msg = f"🚨 Error generating results: {e}\n{traceback.format_exc()}"
|
| 526 |
+
return error_msg, pd.DataFrame(), False, *([None] * (num_outputs - 3))
|
| 527 |
|
| 528 |
# =============================================
|
| 529 |
# Gradio Interface Definition
|
| 530 |
# =============================================
|
| 531 |
+
css = """
|
| 532 |
+
.gradio-container { font-family: 'IBM Plex Sans', sans-serif; }
|
| 533 |
+
footer { display: none !important; }
|
| 534 |
+
/* Responsive adjustments for plots */
|
| 535 |
+
.gr-plot { min-width: 100% !important; }
|
| 536 |
+
@media (max-width: 768px) {
|
| 537 |
+
.gr-row { flex-direction: column !important; }
|
| 538 |
+
.gr-plot { height: 350px !important; margin-bottom: 20px !important; }
|
| 539 |
+
}
|
| 540 |
+
@media (min-width: 769px) {
|
| 541 |
+
.gr-plot { height: 400px !important; }
|
| 542 |
+
}
|
| 543 |
+
"""
|
| 544 |
with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
|
| 545 |
gr.Markdown("# ♟️ Lichess Insights\nAnalyze rated game statistics from Lichess API.")
|
| 546 |
df_state = gr.State(pd.DataFrame())
|
| 547 |
+
has_data = gr.State(False) # State to track if data is available
|
| 548 |
with gr.Row():
|
| 549 |
with gr.Column(scale=1, min_width=250): # Sidebar
|
| 550 |
gr.Markdown("## ⚙️ Settings")
|
|
|
|
| 560 |
with gr.Column(scale=4): # Main Content
|
| 561 |
with gr.Tabs() as tabs:
|
| 562 |
with gr.TabItem("1. Overview", id=0):
|
| 563 |
+
overview_stats_md_out = gr.Markdown(visible=False)
|
| 564 |
+
with gr.Row(visible=False) as overview_row:
|
| 565 |
overview_plot_pie = gr.Plot(label="Overall Results")
|
| 566 |
overview_plot_color = gr.Plot(label="Results by Color")
|
| 567 |
overview_plot_rating = gr.Plot(label="Rating Trend")
|
| 568 |
overview_plot_elo_diff = gr.Plot(label="Elo Advantage vs. Result")
|
| 569 |
with gr.TabItem("2. Perf. Over Time", id=1):
|
| 570 |
+
with gr.Row(visible=False) as perf_time_row:
|
| 571 |
+
time_plot_games_yr = gr.Plot(label="Games per Year")
|
| 572 |
+
time_plot_wr_yr = gr.Plot(label="Win Rate per Year")
|
| 573 |
with gr.TabItem("3. Perf. by Color", id=2):
|
| 574 |
+
color_plot_placeholder = gr.Markdown("(Results by color shown in Overview)", visible=False)
|
| 575 |
with gr.TabItem("4. Time & Date", id=3):
|
| 576 |
gr.Markdown("### Day of Week")
|
| 577 |
+
with gr.Row(visible=False) as dow_row:
|
| 578 |
time_plot_games_dow = gr.Plot(label="Games by Day of Week")
|
| 579 |
time_plot_wr_dow = gr.Plot(label="Win Rate by Day of Week")
|
| 580 |
gr.Markdown("### Hour of Day (UTC)")
|
| 581 |
+
with gr.Row(visible=False) as hod_row:
|
| 582 |
time_plot_games_hod = gr.Plot(label="Games by Hour (UTC)")
|
| 583 |
time_plot_wr_hod = gr.Plot(label="Win Rate by Hour (UTC)")
|
| 584 |
gr.Markdown("### Day of Month")
|
| 585 |
+
with gr.Row(visible=False) as dom_row:
|
| 586 |
time_plot_games_dom = gr.Plot(label="Games by Day of Month")
|
| 587 |
time_plot_wr_dom = gr.Plot(label="Win Rate by Day of Month")
|
| 588 |
gr.Markdown("### Time Control Category")
|
| 589 |
+
time_plot_perf_tc = gr.Plot(label="Performance by Time Control", visible=False)
|
| 590 |
with gr.TabItem("5. ECO & Openings", id=4):
|
| 591 |
gr.Markdown("#### API Names")
|
| 592 |
+
eco_plot_freq_api = gr.Plot(label="Opening Frequency (API)", visible=False)
|
| 593 |
+
eco_plot_wr_api = gr.Plot(label="Opening Win Rate (API)", visible=False)
|
| 594 |
gr.Markdown("---")
|
| 595 |
gr.Markdown("#### Custom Map")
|
| 596 |
if not ECO_MAPPING:
|
| 597 |
gr.Markdown("⚠️ Custom map not loaded.")
|
| 598 |
+
eco_plot_freq_cust = gr.Plot(label="Opening Frequency (Custom)", visible=False)
|
| 599 |
+
eco_plot_wr_cust = gr.Plot(label="Opening Win Rate (Custom)", visible=False)
|
| 600 |
with gr.TabItem("6. Opponents", id=5):
|
| 601 |
+
opp_plot_freq = gr.Plot(label="Frequent Opponents", visible=False)
|
| 602 |
+
opp_df_list = gr.DataFrame(label="Top Opponents List", wrap=True, visible=False)
|
| 603 |
+
opp_plot_elo = gr.Plot(label="Elo Advantage vs Result", visible=False)
|
| 604 |
with gr.TabItem("7. vs Titled", id=6):
|
| 605 |
gr.Markdown("Analysis based on sidebar selection.")
|
| 606 |
+
titled_status = gr.Markdown(visible=False)
|
| 607 |
+
with gr.Row(visible=False) as titled_row:
|
| 608 |
titled_plot_pie = gr.Plot(label="Results vs Selected Titles")
|
| 609 |
titled_plot_color = gr.Plot(label="Results by Color vs Selected Titles")
|
| 610 |
titled_plot_rating = gr.Plot(label="Rating Trend vs Selected Titles")
|
| 611 |
+
titled_df_h2h_comp = gr.DataFrame(label="Head-to-Head vs Selected Titles", wrap=True, visible=False)
|
| 612 |
with gr.TabItem("8. Termination", id=7):
|
| 613 |
gr.Markdown("### Time Forfeit")
|
| 614 |
+
term_plot_tf_summary = gr.Plot(label="Time Forfeit Summary", visible=False)
|
| 615 |
+
term_plot_tf_tc = gr.Plot(label="Time Forfeits by Time Control", visible=False)
|
| 616 |
with gr.Accordion("View Recent TF Games", open=False):
|
| 617 |
+
term_df_tf_list = gr.DataFrame(label="Recent TF Games", wrap=True, visible=False)
|
| 618 |
gr.Markdown("### Overall Termination")
|
| 619 |
+
term_plot_all = gr.Plot(label="Overall Termination", visible=False)
|
| 620 |
+
|
| 621 |
+
# Define visibility updates based on has_data
|
| 622 |
+
def update_visibility(has_data_value, *args):
|
| 623 |
+
visibility = has_data_value
|
| 624 |
+
return (
|
| 625 |
+
gr.update(visible=visibility), # overview_stats_md_out
|
| 626 |
+
gr.update(visible=visibility), # overview_row
|
| 627 |
+
gr.update(visible=visibility), # perf_time_row
|
| 628 |
+
gr.update(visible=visibility), # color_plot_placeholder
|
| 629 |
+
gr.update(visible=visibility), # dow_row
|
| 630 |
+
gr.update(visible=visibility), # hod_row
|
| 631 |
+
gr.update(visible=visibility), # dom_row
|
| 632 |
+
gr.update(visible=visibility), # time_plot_perf_tc
|
| 633 |
+
gr.update(visible=visibility), # eco_plot_freq_api
|
| 634 |
+
gr.update(visible=visibility), # eco_plot_wr_api
|
| 635 |
+
gr.update(visible=visibility), # eco_plot_freq_cust
|
| 636 |
+
gr.update(visible=visibility), # eco_plot_wr_cust
|
| 637 |
+
gr.update(visible=visibility), # opp_plot_freq
|
| 638 |
+
gr.update(visible=visibility), # opp_df_list
|
| 639 |
+
gr.update(visible=visibility), # opp_plot_elo
|
| 640 |
+
gr.update(visible=visibility), # titled_status
|
| 641 |
+
gr.update(visible=visibility), # titled_row
|
| 642 |
+
gr.update(visible=visibility), # titled_df_h2h_comp
|
| 643 |
+
gr.update(visible=visibility), # term_plot_tf_summary
|
| 644 |
+
gr.update(visible=visibility), # term_plot_tf_tc
|
| 645 |
+
gr.update(visible=visibility), # term_df_tf_list
|
| 646 |
+
gr.update(visible=visibility), # term_plot_all
|
| 647 |
+
)
|
| 648 |
+
|
| 649 |
+
outputs_list = [
|
| 650 |
+
status_output, df_state, has_data,
|
| 651 |
+
overview_plot_pie, overview_stats_md_out, overview_plot_color, overview_plot_rating, overview_plot_elo_diff,
|
| 652 |
+
time_plot_games_yr, time_plot_wr_yr, color_plot_placeholder, time_plot_games_dow, time_plot_wr_dow,
|
| 653 |
+
time_plot_games_hod, time_plot_wr_hod, time_plot_games_dom, time_plot_wr_dom, time_plot_perf_tc,
|
| 654 |
+
eco_plot_freq_api, eco_plot_wr_api, eco_plot_freq_cust, eco_plot_wr_cust, opp_plot_freq, opp_df_list,
|
| 655 |
+
opp_plot_elo, titled_status, titled_plot_pie, titled_plot_color, titled_plot_rating, titled_df_h2h_comp,
|
| 656 |
+
term_plot_tf_summary, term_plot_tf_tc, term_df_tf_list, term_plot_all
|
| 657 |
+
]
|
| 658 |
+
visibility_outputs = [
|
| 659 |
+
overview_stats_md_out, overview_row, perf_time_row, color_plot_placeholder, dow_row, hod_row, dom_row,
|
| 660 |
+
time_plot_perf_tc, eco_plot_freq_api, eco_plot_wr_api, eco_plot_freq_cust, eco_plot_wr_cust, opp_plot_freq,
|
| 661 |
+
opp_df_list, opp_plot_elo, titled_status, titled_row, titled_df_h2h_comp, term_plot_tf_summary, term_plot_tf_tc,
|
| 662 |
+
term_df_tf_list, term_plot_all
|
| 663 |
+
]
|
| 664 |
+
analyze_btn.click(fn=perform_full_analysis, inputs=[username_input, time_period_input, perf_type_input, titled_player_select], outputs=outputs_list).then(
|
| 665 |
+
fn=update_visibility, inputs=[has_data] + outputs_list[3:], outputs=visibility_outputs
|
| 666 |
+
)
|
| 667 |
|
| 668 |
# --- Launch the Gradio App ---
|
| 669 |
if __name__ == "__main__":
|