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