CoolFace
Apppublic

TJStatsApps/2025_decision_value

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
pitcher_scatter.py508 linesDownload Raw Back to root
1from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui2import datasets3from datasets import load_dataset4import pandas as pd5import numpy as np6import matplotlib.pyplot as plt7import seaborn as sns8import numpy as np9from scipy.stats import gaussian_kde10import matplotlib11from matplotlib.ticker import MaxNLocator12from matplotlib.gridspec import GridSpec13from scipy.stats import zscore14import math15import matplotlib16from adjustText import adjust_text17import matplotlib.ticker as mtick18from shinywidgets import output_widget, render_widget19import pandas as pd20from configure import base_url21import shinyswatch22import inflect23from matplotlib.pyplot import text24 25 26exit_velo_df_codes_summ = pd.read_csv('summary_pitcher.csv',index_col=[0]).reset_index(drop=True)27exit_velo_df_codes_summ_non_level = pd.read_csv('summary_pitcher_level.csv',index_col=[0]).reset_index(drop=True)28 29exit_velo_df_codes_summ_non_level['levels'] = exit_velo_df_codes_summ_non_level.levels.str.split(', ')30 31exit_velo_df_codes_summ_non_level = exit_velo_df_codes_summ_non_level.rename(columns={'levels':'level'})32 33print(exit_velo_df_codes_summ.bb_minus_k_percent)34exit_velo_df_codes_summ.bb_minus_k_percent = -1*exit_velo_df_codes_summ.bb_minus_k_percent35exit_velo_df_codes_summ.bb_over_k_percent = exit_velo_df_codes_summ.k_percent/exit_velo_df_codes_summ.bb_percent36 37exit_velo_df_codes_summ_non_level.bb_minus_k_percent = -1*exit_velo_df_codes_summ_non_level.bb_minus_k_percent38exit_velo_df_codes_summ_non_level.bb_over_k_percent = exit_velo_df_codes_summ_non_level.k_percent/exit_velo_df_codes_summ_non_level.bb_percent39 40pitcher_dict_stat = { 'sweet_spot_percent':{'x_axis':'SweetSpot%','title':'SweetSpot%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},41                'max_launch_speed':{'x_axis':'Max Exit Velocity','title':'Max Exit Velocity','flip_p':False,'decimal_format':'string_0','percent_adjust':1},42                'launch_speed_90':{'x_axis':'90th Percentile EV','title':'90th Percentile EV','flip_p':False,'decimal_format':'string_0','percent_adjust':1},43                'launch_speed':{'x_axis':'Exit Velocity','title':'Exit Velocity','flip_p':False,'decimal_format':'string_0','percent_adjust':1},44                'launch_angle':{'x_axis':'Launch Angle','title':'Launch Angle','flip_p':False,'decimal_format':'string_0','percent_adjust':100},45                'avg':{'x_axis':'AVG','title':'AVG','flip_p':False,'decimal_format':'string_3','percent_adjust':100},46                'obp':{'x_axis':'OBP','title':'OBP','flip_p':False,'decimal_format':'string_3','percent_adjust':100},47                'slg':{'x_axis':'SLG','title':'SLG','flip_p':False,'decimal_format':'string_3','percent_adjust':100},48                'ops':{'x_axis':'OPS','title':'OPS','flip_p':False,'decimal_format':'string_3','percent_adjust':100},49                'k_percent':{'x_axis':'K%','title':'K%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},50                'bb_percent':{'x_axis':'BB%','title':'BB%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},51                'bb_over_k_percent':{'x_axis':'K/BB','title':'K/BB','flip_p':True,'decimal_format':'string_1','percent_adjust':100},52                'bb_minus_k_percent':{'x_axis':'K%-BB%','title':'K%-BB%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},53                'csw_percent':{'x_axis':'CSW%','title':'CSW%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},54                'woba_percent':{'x_axis':'wOBA','title':'wOBA','flip_p':False,'decimal_format':'string_3','percent_adjust':100},55                'hard_hit_percent':{'x_axis':'HardHit%','title':'HardHit%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},56                'barrel_percent':{'x_axis':'Barrel%','title':'Barrel%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},57                'zone_contact_percent':{'x_axis':'Z-Contact%','title':'Z-Contact%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},58                'zone_swing_percent':{'x_axis':'Z-Swing%','title':'Z-Swing%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},59                'zone_percent':{'x_axis':'Zone%','title':'Zone%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},60                'chase_percent':{'x_axis':'O-Swing%','title':'O-Swing%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},61                'chase_contact':{'x_axis':'O-Contact%','title':'O-Contact%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},62                'swing_percent':{'x_axis':'Swing%','title':'Swing%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},63                'whiff_rate':{'x_axis':'Whiff%','title':'Whiff%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},64                'swstr_rate':{'x_axis':'SwStr%','title':'SwStr%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},65                }66 67pitcher_dict_stat_small = { 'sweet_spot_percent':'SweetSpot%',68                'max_launch_speed':'Max Exit Velocity',69                'launch_speed_90':'90th Percentile EV',70                'launch_speed':'Exit Velocity',71                'launch_angle':'Launch Angle',72                'avg':'AVG',73                'obp':'OBP',74                'slg':'SLG',75                'ops':'OPS',76                'k_percent':'K%',77                'bb_percent':'BB%',78                'bb_over_k_percent':'K/BB',79                'bb_minus_k_percent':'K%-BB%',80                'csw_percent':'CSW%',81                'woba_percent':'wOBA',82                'hard_hit_percent':'HardHit%',83                'barrel_percent':'Barrel%',84                'zone_contact_percent':'Z-Contact%',85                'zone_swing_percent':'Z-Swing%',86                'zone_percent':'Zone%',87                'chase_percent':'O-Swing%',88                'chase_contact':'O-Contact%',89                'swing_percent':'Swing%',90                'whiff_rate':'Whiff%',91                'swstr_rate':'SwStr%',92                }93 94 95colour_palette = ['#FFB000','#648FFF','#785EF0',96                  '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']97 98level_dict = {'MLB':'MLB','AAA':'AAA','AA':'AA','A+':'A+','A':'A','ROK':'ROK'}99 100batter_test_df = exit_velo_df_codes_summ.sort_values(by='pitcher').drop_duplicates(subset='pitcher_id').reset_index(drop=True)[['pitcher_id','pitcher']]#['pitcher'].to_dict()101batter_test_df = batter_test_df.set_index('pitcher_id')102 103 104def decimal_format_assign(x):105    if x['decimal_format'] == 'percent_1':106        return mtick.PercentFormatter(1,decimals=1)107    if x['decimal_format'] == 'string_3':108        return mtick.FormatStrFormatter('%.3f')109    if x['decimal_format'] == 'string_0':110        return mtick.FormatStrFormatter('%.0f')111    if x['decimal_format'] == 'string_1':112        return mtick.FormatStrFormatter('%.1f')113 114 115#test_df =  test_df[test_df.pitcher == 'Chris Bassitt'].append(test_df[test_df.pitcher != 'Chris Bassitt'])116 117pitcher_dict = batter_test_df['pitcher'].to_dict()118 119exit_velo_df_codes_summ.position = exit_velo_df_codes_summ.position.replace(['TWP'],['P'])120 121exit_velo_df_codes_summ_non_level.position = exit_velo_df_codes_summ_non_level.position.replace(['TWP'],['P'])122 123 124position_list = ['All'] + list(exit_velo_df_codes_summ.position.unique())125team_list = ['All'] + sorted(list(exit_velo_df_codes_summ.parent_org_abb.unique()))126 127 128 129def server(input,output,session):130 131 132    @output133    @render.plot(alt="A histogram")134    @reactive.event(input.go, ignore_none=False)135    def plot():136        sns.set_theme(style="whitegrid", palette="pastel")137 138 139        print(input.level_id())140        print(input.n())141        print('we made it here')142 143        #data_df = exit_velo_df_codes_summ.copy()144 145 146        if input.group_level():147            data_df = exit_velo_df_codes_summ_non_level.copy()148 149            turth_list = []150            #turth_list_2 = []151            for x in range(0,len(data_df.level)):152                turth_list_2 = []153                for y in range(0,len(data_df.level[x])):154                    #print(level_list[x][y])155                    turth_list_2.append(data_df.level[x][y] in input.level_id())156                turth_list.append(turth_list_2)157 158            final_check_list = [True if True in x else False for x in turth_list]159 160 161            data_df = data_df[(data_df.pa >= input.n())&(data_df.age <= input.n_age())&(final_check_list)]162        163        else:164            data_df = exit_velo_df_codes_summ.copy()165            166            data_df = data_df[(data_df.pa >= input.n())&(data_df.age <= input.n_age())&(data_df.level.isin(input.level_id()))]167            print(data_df)168            if 'All' in input.team_id():169                print('nice')#data_df = data_df[(data_df.pa >= input.n())&(data_df.age <= input.n_age())].reset_index(drop=True)170            171            else:172                data_df = data_df[(data_df.parent_org_abb.isin(input.team_id()))].reset_index(drop=True)173 174            if 'All' in input.position_id():175                print('nice')#data_df = data_df[(data_df.level.isin(input.level_id()))&(data_df.pa >= input.n())&(data_df.age <= input.n_age())].reset_index(drop=True)176            177            else:178                data_df = data_df[(data_df.position.isin(input.position_id()))].reset_index(drop=True)179        180        181                182 183 184 185        data_df = data_df.sort_values(by='level').reset_index(drop=True)186        print(pitcher_dict_stat[input.stat_x()]['flip_p'])187 188 189 190        x_flip = pitcher_dict_stat[input.stat_x()]['flip_p']191        y_flip = pitcher_dict_stat[input.stat_y()]['flip_p']192        cbr_flip = pitcher_dict_stat[input.stat_z()]['flip_p']193 194        195 196        data_df[input.stat_x()+'_percent'] = data_df[input.stat_x()].rank(pct=True,ascending=abs(x_flip-1))197 198        data_df[input.stat_y()+'_percent'] = data_df[input.stat_y()].rank(pct=True,ascending=abs(y_flip-1))199 200        data_df[input.stat_z()+'_percent'] = data_df[input.stat_z()].rank(pct=True,ascending=abs(cbr_flip-1))201 202 203 204        fig, ax = plt.subplots(1, 1, figsize=(9, 9))205 206        #data_df['bb_over_obp'] = data_df['bb']/data_df['k']207        208        #data_df[input.stat_z()]= data_df[input.stat_z()].fillna(-100000)209        210        211        if not cbr_flip:212            cmap_hue = matplotlib.colors.LinearSegmentedColormap.from_list("", [colour_palette[0],colour_palette[3],colour_palette[1]])213            norm = plt.Normalize(data_df[input.stat_z()].min(), data_df[input.stat_z()].max())214 215        else:216            cmap_hue = matplotlib.colors.LinearSegmentedColormap.from_list("", [colour_palette[1],colour_palette[3],colour_palette[0]])217            norm = plt.Normalize(data_df[input.stat_z()].min(), data_df[input.stat_z()].max())218 219        sm = plt.cm.ScalarMappable(cmap=cmap_hue, norm=norm)220        print('we made it here')221 222        # sns.regplot(x = stat_x, y = stat_y, data=data_df, color = colour_palette[6],ax=ax,scatter=False,223        #             line_kws=dict(alpha=0.3,linewidth=2,zorder=1))224        # scatter_plot = sns.scatterplot(x = stat_x, y = stat_y, data=data_df, color = colour_palette[0],ax=ax,hue=stat_z,palette=cmap_hue)225 226 227 228        # r, p = sp.stats.pearsonr(data_df[input.stat_x()], data_df[input.stat_y()])229        # ax = plt.gca()230        # # ax.text(.25, 0.3, 'r={:.2f}, p={:.2g}'.format(r, p),231        # #         transform=ax.transAxes, fontsize=12)232 233        # ax.annotate('R²={:.2f}'.format(r, p), ( math.ceil(data_df[input.stat_x()].max()*pitcher_dict_stat[input.stat_x()]['percent_adjust']/5)*5/pitcher_dict_stat[input.stat_x()]['percent_adjust']*(1-pitcher_dict_stat[input.stat_x()]['flip_p']), 234        #                                       math.floor(data_df[input.stat_y()].min()*pitcher_dict_stat[input.stat_y()]['percent_adjust']/5)*5/pitcher_dict_stat[input.stat_y()]['percent_adjust']*(1-pitcher_dict_stat[input.stat_y()]['flip_p'])), 235        #                                         fontsize=18,fontname='Century Gothic',ha='right')236 237        if input.group_level():238            scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = '#b3b3b3')239            #ax.get_legend().remove()240            scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = colour_palette[0],ax=ax,hue=input.stat_z(),palette=cmap_hue)241        else:242            scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = '#b3b3b3',style='level')    243            #ax.get_legend().remove()244            scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = colour_palette[0],ax=ax,hue=input.stat_z(),palette=cmap_hue,style='level')245 246        247        sns.set_theme(style="whitegrid", palette="pastel")248 249        fig.set_facecolor('#F0F0F0')250        ax.set_facecolor('white')251 252        print('we made it here!')253        # for i in range(0,len(pitch_group_unique)):254        #     data_df = elly_zone_df[elly_zone_df.pitch_group==pitch_group_unique[i]]255        #     len_df.append(len(data_df))256        #     sns.lineplot(x=range(1,len(data_df)+1),y=data_df.swings.rolling(window=rolling_window_input).sum()/data_df.pitches.rolling(window=rolling_window_input).sum(),color=colour_palette[i],linewidth=3,ax=ax,257        #                  label=f'{pitch_group_unique[i]} (Season Average {float(data_df.swings.sum()/data_df.pitches.sum()):.1%})',zorder=i+10)258        #     ax.hlines(xmin=0,xmax=len(elly_zone_df),y=data_df.swings.sum()/data_df.pitches.sum(),color=colour_palette[i],linewidth=3,linestyle='-.',alpha=0.4,zorder=i)259 260        ts=[]261        xs=[]262        ys=[]263        labels=[]264 265        print(input.player_id())266        if input.names():267            for i in range(len(data_df)):268                if (data_df[input.stat_x()+'_percent'][i] < input.n_percent_bot_x()  or data_df[input.stat_x()+'_percent'][i] > 1 - input.n_percent_top_x() ) \269                or (data_df[input.stat_y()+'_percent'][i] < input.n_percent_bot_y()  or data_df[input.stat_y()+'_percent'][i] > 1 -input.n_percent_top_y()) \270                or (data_df[input.stat_z()+'_percent'][i] < input.n_percent_bot_z()  or data_df[input.stat_z()+'_percent'][i] > 1 -input.n_percent_top_z() )\271                or (str(data_df.pitcher_id[i]) in (input.player_id())):272                    ts.append(ax.text(data_df[input.stat_x()][i], data_df[input.stat_y()][i], data_df.pitcher[i],fontsize=8))273 274 275                    # ax.annotate(data_df.pitcher[i], xy=((data_df[input.stat_x()][i])+0.025/pitcher_dict_stat[input.stat_x()]['percent_adjust'], data_df[input.stat_y()][i]+0.01/pitcher_dict_stat[input.stat_x()]['percent_adjust']), xytext=(-20,20), 276                    # textcoords='offset points', ha='center', va='bottom',fontsize=7,277                    # bbox=dict(boxstyle='round,pad=0', fc=colour_palette[6], alpha=0.0),278                    # arrowprops=dict(arrowstyle='->', connectionstyle="angle,angleA=-90,angleB=-10,rad=5", 279                    #                 color=colour_palette[4]))280                    281        #             print('CONDITION CHEK')282        #             print(data_df[input.stat_x()][i])283        #             #if data_df['pitcher'][i] != 'Jo Adell':284        #             #ax.annotate(data_df.pitcher[i], ((data_df[input.stat_x()][i])+0.025/pitcher_dict_stat[input.stat_x()]['percent_adjust'], data_df[input.stat_y()][i]+0.01/pitcher_dict_stat[input.stat_x()]['percent_adjust']),fontsize=8)285        #             #ts.append(data_df[input.stat_x()][i], data_df[input.stat_y()][i], data_df.pitcher[i],fontsize=8))286        #             xs.append(data_df[input.stat_x()][i])287        #             ys.append(data_df[input.stat_y()][i])288        #             labels.append(data_df.pitcher[i])289 290        # # adjust_text(ts, force_points=0.2, force_text=0.2,291        # #             expand_points=(1, 1), expand_text=(1, 1),292        # #             arrowprops=dict(arrowstyle="-", color='black', lw=0))293        # #print(xs)294        # if len(xs)>0: 295        #     repel_labels(ax=ax, x = xs,y = ys, labels = labels, k=0.0025)296 297        ax.hlines(xmin=math.floor((data_df[input.stat_x()].min()*pitcher_dict_stat[input.stat_x()]['percent_adjust']-0.01)/5)*5/pitcher_dict_stat[input.stat_x()]['percent_adjust'],298                    xmax= math.ceil((data_df[input.stat_x()].max()*pitcher_dict_stat[input.stat_x()]['percent_adjust']+0.01)/5)*5/pitcher_dict_stat[input.stat_x()]['percent_adjust'],299                    y=data_df[input.stat_y()].mean(),color='gray',linewidth=3,linestyle='dotted',alpha=0.4)300 301        print('we made it here')302        ax.vlines(ymin=(math.floor(data_df[input.stat_y()].min()*pitcher_dict_stat[input.stat_y()]['percent_adjust']-0.01)/5)*5/pitcher_dict_stat[input.stat_y()]['percent_adjust'],303                    ymax= (math.ceil(data_df[input.stat_y()].max()*pitcher_dict_stat[input.stat_y()]['percent_adjust']+0.01)/5)*5/pitcher_dict_stat[input.stat_y()]['percent_adjust'],304                    x=data_df[input.stat_x()].mean(),color='gray',linewidth=3,linestyle='dotted',alpha=0.4)305 306        ax.set_xlim((math.floor(data_df[input.stat_x()].min()*pitcher_dict_stat[input.stat_x()]['percent_adjust']-0.01)/5)*5/pitcher_dict_stat[input.stat_x()]['percent_adjust'],307                    (math.ceil(data_df[input.stat_x()].max()*pitcher_dict_stat[input.stat_x()]['percent_adjust']+0.01)/5)*5/pitcher_dict_stat[input.stat_x()]['percent_adjust'])308 309        ax.set_ylim((math.floor(data_df[input.stat_y()].min()*pitcher_dict_stat[input.stat_y()]['percent_adjust']-0.01)/5)*5/pitcher_dict_stat[input.stat_y()]['percent_adjust'],310                    (math.ceil(data_df[input.stat_y()].max()*pitcher_dict_stat[input.stat_y()]['percent_adjust']+0.01)/5)*5/pitcher_dict_stat[input.stat_y()]['percent_adjust'])311        312        title_level = str([x .strip("\'")for x in input.level_id()]).strip('[').strip(']').replace("'",'')313 314        if title_level == 'AAA, AA, A+, A':315            title_level='MiLB'316 317 318        if input.n_age() >= 50:319            title_spot = f'{title_level} Pitcher {pitcher_dict_stat[input.stat_y()]["title"]} vs {pitcher_dict_stat[input.stat_x()]["title"]} (min. {input.n()} PA)'320        321        else:322            title_spot = f'{title_level} Pitcher {pitcher_dict_stat[input.stat_y()]["title"]} vs {pitcher_dict_stat[input.stat_x()]["title"]} (min. {input.n()} PA, Max Age {input.n_age()})'323 324        #title_level = input.level_id()[0]325        #title_spot = f'{title_level} pitcher {pitcher_dict_stat[input.stat_y()]["title"]} vs {pitcher_dict_stat[input.stat_x()]["title"]} (min. {input.n()} PA)'326        327        ax.set_title(title_spot, fontsize=24/(len(title_spot)*0.03),fontname='Century Gothic')328        # #vals = ax.get_yticks()329        ax.set_xlabel(pitcher_dict_stat[input.stat_x()]['x_axis'], fontsize=16,fontname='Century Gothic')330        ax.set_ylabel(pitcher_dict_stat[input.stat_y()]['x_axis'], fontsize=16,fontname='Century Gothic')331        332 333        if input.group_level():334            ax.get_legend().remove()335 336        if not input.group_level():337            if len(input.level_id()) > 1:338                h,l = scatter.get_legend_handles_labels()339                l[-(len(input.level_id())+1)] = 'Level'340                ax.legend(h[-(len(input.level_id())+1):],l[-(len(input.level_id())+1):], borderaxespad=0.1,loc=0)341 342            else:343                ax.get_legend().remove()344 345 346 347        #plt.show(g)348        # ax.figure.colorbar(sm, ax=ax)349        350        cbar  = ax.figure.colorbar(sm, ax=ax,format=decimal_format_assign(x=pitcher_dict_stat[input.stat_z()]),orientation='vertical',aspect=30)351        cbar.set_label(pitcher_dict_stat[input.stat_z()]['x_axis'])352        #fig.axes[0].invert_yaxis()353        print('we made it here5')354        fig.subplots_adjust(wspace=.02, hspace=.02)355        # ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: int(x)))356        #ax.set_yticks([0,0.1,0.2,0.3,0.4,0.5])357        # fig.colorbar(plot_dist, ax=ax)358        # fig.colorbar(plot_dist)359 360        if not pitcher_dict_stat[input.stat_x()]['flip_p']:361            fig.axes[0].invert_xaxis()362 363        if not pitcher_dict_stat[input.stat_y()]['flip_p']:364            fig.axes[0].invert_yaxis()365 366 367        # ax.xaxis.set_major_formatter(mtick.PercentFormatter(1,decimals=0))368        # ax.yaxis.set_major_formatter(mtick.PercentFormatter(1))369 370        371        372        373 374        print('we made it here6')375 376        ax.xaxis.set_major_formatter(decimal_format_assign(x=pitcher_dict_stat[input.stat_x()]))377        ax.yaxis.set_major_formatter(decimal_format_assign(x=pitcher_dict_stat[input.stat_y()]))378 379 380        print('we made it here7')381        # ax.text(0.5, 0.5, '/u/tomstoms', transform=ax.transAxes,382        #         fontsize=60, color='gray', alpha=0.075,383        #         ha='center', va='center', rotation=45)384        adjust_text(ts,385                    arrowprops=dict(arrowstyle="-", color=colour_palette[4], lw=1),ax=ax)386 387        #ax.legend(fontsize='16')388        fig.text(x=0.03,y=0.02,s='By: @TJStats',fontname='Century Gothic')389        fig.text(x=1-0.03,y=0.02,s='Data: MLB',ha='right',fontname='Century Gothic')390        fig.tight_layout()391 392 393pitcher_scatter = App(ui.page_fluid(394    ui.tags.base(href=base_url), 395    ui.tags.div(396         {"style": "width:90%;margin: 0 auto;max-width: 1600px;"},397        ui.tags.style(398            """399            h4 {400                margin-top: 1em;font-size:35px;401            }402            h2{403                font-size:25px;404            }405            """406         ),407    shinyswatch.theme.simplex(),408    ui.tags.h4("TJStats"),409    ui.tags.i("Baseball Analytics and Visualizations"),410    ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),411    ui.navset_tab(412        ui.nav_control(413             ui.a(414                "Home",415                href="home/"416            ),417        ),418        ui.nav_menu(419            "Batter Charts",420            ui.nav_control(421            ui.a(422                "Batting Rolling",423                href="rolling_batter/"424            ),425            ui.a(426                "Spray & Damage",427                href="spray/"428            ),429            ui.a(430                "Decision Value",431                href="decision_value/"432            ),433            # ui.a(434            #     "Damage Model",435            #     href="damage_model/"436            # ),437            ui.a(438                "Batter Scatter",439                href="batter_scatter/"440            ),441            # ui.a(442            #     "EV vs LA Plot",443            #     href="ev_angle/"444            # ),445            ui.a(446                "Statcast Compare",447                href="statcast_compare/"448            )449        ),450        ),451        ui.nav_menu(452            "Pitcher Charts",453            ui.nav_control(454             ui.a(455                "Pitcher Rolling",456                href="rolling_pitcher/"457            ),458             ui.a(459                "Pitcher Summary",460                href="pitching_summary_graphic_new/"461            ),462             ui.a(463                "Pitcher Scatter",464                href="pitcher_scatter/"465            )466        ),467        )),ui.row(468    ui.layout_sidebar(469        470   471 472      ui.panel_sidebar(473        #ui.input_select("id", "Select Batter",batter_dict,selected=675911,width=1,size=1),474        ui.row(475            ui.column(4,ui.input_select("level_id", "Select Level",level_dict,width=1,size=1,multiple=True,selected='MLB',selectize=True),),476            ui.column(4,ui.input_select("team_id", "Select Team",team_list,width=1,size=1,multiple=True,selected='All',selectize=True),),477            ui.column(4,ui.input_select("position_id", "Select Position",position_list,width=1,size=1,selected='All',multiple=True,selectize=True))),478        ui.row(479            ui.column(6,ui.input_numeric("n", "Minimum PA", value=100)),480            ui.column(6,ui.input_numeric("n_age", "Maximum Age", value=50))),481        ui.row(482            ui.column(4,ui.input_select("stat_x", "X-Axis",pitcher_dict_stat_small,selected='k_percent',width=1,size=1)),483            ui.column(4,ui.input_select("stat_y", "Y-Axis",pitcher_dict_stat_small,selected='bb_percent',width=1,size=1)),484            ui.column(4,ui.input_select("stat_z", "Colour-Bar Axis",pitcher_dict_stat_small,selected='bb_minus_k_percent',width=1,size=1))),485       486        ui.row(487            ui.column(6,ui.input_numeric("n_percent_top_x", "Top 'n' Percentile X-Labels", value=0.01)),488            ui.column(6,ui.input_numeric("n_percent_bot_x", "Bottom 'n' Percentile X-Labels", value=0.01))),489        ui.row(490            ui.column(6,ui.input_numeric("n_percent_top_y", "Top 'n' Percentile Y-Labels", value=0.01)),491            ui.column(6,ui.input_numeric("n_percent_bot_y", "Bottom 'n' Percentile Y-Labels", value=0.01))),492        ui.row(493            ui.column(6,ui.input_numeric("n_percent_top_z", "Top 'n' Percentile Z-Labels", value=0.01)),494            ui.column(6,ui.input_numeric("n_percent_bot_z", "Bottom 'n' Percentile Z-Labels", value=0.01))),495 496        ui.input_select("player_id", "Label Player",pitcher_dict,width=1,size=1,multiple=True,selectize=True),497        ui.row(498            ui.input_switch("names", "Toggle Names"),499            ui.input_switch("group_level", "Group Levels")),500        ui.input_action_button("go", "Generate",class_="btn-primary"),501     ),502 503 504      ui.panel_main(505        ui.output_plot("plot",height = "1000px",width="1000px")506      ,507    ),508    )),)),server)