import gradio as gr # import pandas as pd import polars as pl from math import ceil import os from data import df, pitch_stats, league_pitch_stats, player_df from gradio_function import * from translate import jp_pitch_to_en_pitch, max_pitch_types from css import css os.makedirs('files', exist_ok=True) def create_pitcher_dashboard(): with gr.Blocks( css=css ) as demo: gr.Markdown(''' # NPB data visualization demo [Data from SportsNavi](https://sports.yahoo.co.jp/) ''') source_df = gr.State(df) app_df = gr.State(df) app_league_df = gr.State(df) app_pitch_stats = gr.State(pitch_stats) app_league_pitch_stats = gr.State(league_pitch_stats) with gr.Row(): player = gr.Dropdown(value=None, choices=sorted(player_df.filter(pl.col('name').is_not_null())['name'].to_list()), label='Player') handedness = gr.Radio(value='Both', choices=['Both', 'Left', 'Right'], type='value', interactive=False, label='Batter Handedness') # preview = gr.DataFrame() download_file = gr.DownloadButton(label='Download player data') with gr.Group(): with gr.Row(): usage = gr.Plot(label='Pitch usage') velo_summary = gr.Plot(label='Velocity summary', elem_classes='pitch-velo-summary') loc_summary = gr.Plot(label='Overall location') gr.Markdown('## Pitch Velocity') velo_stats = gr.DataFrame(pl.DataFrame([{'Avg. Velo': None, 'League Avg. Velo': None}]), interactive=False, label='Pitch Velocity') max_locs = len(jp_pitch_to_en_pitch) locs_per_row = 4 max_rows = ceil(max_locs/locs_per_row) gr.Markdown(''' ## Pitch Locations Pitcher's persective <br> `NPB` refers to the top 10% of pitches thrown across the league with the current search constraints e.g. handedness <br> Note: To speed up the KDE, we restrict the league-wide pitches to 5,000 pitches ''') pitch_rows = [] pitch_groups = [] pitch_names = [] pitch_infos = [] pitch_velos = [] pitch_locs = [] for row in range(max_rows): visible = row==0 pitch_row = gr.Row(visible=visible) pitch_rows.append(pitch_row) with pitch_row: _locs_per_row = locs_per_row if row < max_rows-1 else max_locs - locs_per_row * (max_rows - 1) for col in range(_locs_per_row): with gr.Column(min_width=256): pitch_group = gr.Group(visible=visible) pitch_groups.append(pitch_group) with pitch_group: pitch_names.append(gr.Markdown(f'### Pitch {col+1}', visible=visible)) pitch_infos.append(gr.DataFrame(pl.DataFrame([{'Whiff%': None, 'CSW%': None}]), interactive=False, visible=visible)) pitch_velos.append(gr.Plot(show_label=False, elem_classes='pitch-velo', visible=visible)) pitch_locs.append(gr.Plot(label='Pitch Location', elem_classes='pitch-loc', visible=visible)) download_file_fn = create_set_download_file_fn('files/player.csv') plot_loc_summary = lambda df, handedness: plot_loc(df, handedness) fn_configs = { download_file_fn: dict(inputs=[], outputs=download_file), plot_usage: dict(inputs=[player], outputs=usage), plot_velo_summary: dict(inputs=[app_league_df, player], outputs=velo_summary), plot_loc_summary: dict(inputs=[handedness], outputs=loc_summary), plot_pitch_cards: dict(inputs=[app_league_df, app_pitch_stats, handedness], outputs=pitch_rows+pitch_groups+pitch_names+pitch_infos+pitch_velos+pitch_locs) } for k in fn_configs.keys(): fn_configs[k]['df'] = gr.State(df) fn_configs[k]['inputs'] = [fn_configs[k]['df']] + fn_configs[k]['inputs'] ( player .input(update_dfs, inputs=[player, handedness, source_df], outputs=[app_df, app_league_df, app_pitch_stats, app_league_pitch_stats]) .then(lambda : gr.update(value='Both', interactive=True), outputs=handedness) ) handedness.input(update_dfs, inputs=[player, handedness, source_df], outputs=[app_df, app_league_df, app_pitch_stats, app_league_pitch_stats]) # app_df.change(preview_df, inputs=app_df, outputs=preview) # app_df.change(set_download_file, inputs=app_df, outputs=download_file) # app_df.change(plot_usage, inputs=[app_df, player], outputs=usage) # app_df.change(plot_velo_summary, inputs=[app_df, app_league_df, player], outputs=velo_summary) # app_df.change(lambda df: plot_loc(df), inputs=app_df, outputs=loc_summary) # app_df.change(plot_pitch_cards, inputs=[app_df, app_pitch_stats], outputs=pitch_rows+pitch_groups+pitch_names+pitch_infos+pitch_velos+pitch_locs) app_pitch_stats.change(update_velo_stats, inputs=[app_pitch_stats, app_league_pitch_stats], outputs=velo_stats) # ( # app_df # .change(create_set_download_file_fn('files/player.csv'), inputs=app_df, outputs=download_file) # .then(plot_usage, inputs=[app_df, player], outputs=usage) # .then(plot_velo_summary, inputs=[app_df, app_league_df, player], outputs=velo_summary) # .then(lambda df: plot_loc(df), inputs=app_df, outputs=loc_summary) # .then(plot_pitch_cards, inputs=[app_df, app_league_df, app_pitch_stats], outputs=pitch_rows+pitch_groups+pitch_names+pitch_infos+pitch_velos+pitch_locs) # ) app_df.change(lambda df: copy_dataframe(df, len(fn_configs)), inputs=app_df, outputs=[config['df'] for config in fn_configs.values()]) for fn, config in fn_configs.items(): config['df'].change(fn, inputs=config['inputs'], outputs=config['outputs']) gr.Markdown('## Bugs and other notes') with gr.Accordion('Click to open', open=False): gr.Markdown(''' - Y axis ticks messy when no velocity distribution is plotted - DataFrame precision inconsistent ''' ) return demo if __name__ == '__main__': create_pitcher_dashboard().launch( share=True, debug=True )