File size: 6,616 Bytes
3d0c0d1
 
 
eaf2663
3d0c0d1
 
 
eaf2663
3d0c0d1
 
 
 
eaf2663
3d0c0d1
 
 
 
 
 
 
b260f20
3a007f9
3d0c0d1
 
 
 
 
 
 
 
 
 
 
 
 
eaf2663
 
3d0c0d1
eaf2663
3d0c0d1
 
 
 
 
 
 
 
 
 
306970a
 
 
3d0c0d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65393ab
79797b0
65393ab
 
 
 
79797b0
 
65393ab
 
 
 
 
eaf2663
 
 
 
 
 
3d0c0d1
 
eaf2663
3d0c0d1
eaf2663
3d0c0d1
eaf2663
 
 
3d0c0d1
 
 
 
 
 
 
 
 
65393ab
 
 
 
 
 
 
 
 
 
 
 
 
 
3d0c0d1
 
 
 
 
 
 
 
3a007f9
3d0c0d1
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

import gradio as gr
# import pandas as pd
from gradio_calendar import Calendar
import polars as pl

from math import ceil
import datetime
import os

from data import df, pitch_stats, league_pitch_stats, player_df
from gradio_function import *
from seasons import LATEST_SEASON
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')
      start_date = Calendar(value=f'{LATEST_SEASON}-03-01', type='datetime', label='Start Date')
      end_date = Calendar(value=f'{LATEST_SEASON}-11-30', type='datetime', label='End Date')
      handedness = gr.Radio(value='Both', choices=['Both', 'Left', 'Right'], type='value', interactive=False, label='Batter Handedness')
    gr.Markdown('Note: We do not have spring training data, or 2024 postseason data')

    # 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']

    update_dfs_kwargs = dict(
        fn=update_dfs,
        inputs=[player, handedness, start_date, end_date, source_df],
        outputs=[app_df, app_league_df, app_pitch_stats, app_league_pitch_stats]
    )
    non_player_search_inputs = [handedness, start_date, end_date]
    (
        player
        .input(**update_dfs_kwargs)
        .then(lambda : gr.update(value='Both', interactive=True), outputs=handedness)
        # .then(lambda: [gr.update(interactive=True) for _ in range(len(non_player_search_inputs))], outputs=non_player_search_inputs) # breaks Calendar for some reason
    )
    for component in non_player_search_inputs:
        component.input(**update_dfs_kwargs)
    # start_date.input(**update_dfs_kwargs)

    # 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
  )