/* BT player pages: pitch types & velocity v1.3 (placed right after the stats/Statcast section) — on-demand Savant pitch-by-pitch breakdown under every player page */ (function(){var host=document.getElementById('bt-player');if(!host)return;var id=+host.getAttribute('data-mlbam');if(!id)return; var now=new Date(),CUR=now.getMonth()<=2?now.getFullYear()-1:now.getFullYear(),MINY=2015,PIT=null,DATA={}; var PT={FF:'4-Seam',SI:'Sinker',FC:'Cutter',SL:'Slider',ST:'Sweeper',CU:'Curveball',KC:'Knuckle Curve',CH:'Changeup',FS:'Splitter',SV:'Slurve',KN:'Knuckleball',FO:'Forkball',SC:'Screwball',CS:'Slow Curve',EP:'Eephus',FA:'Fastball'}; var SWING={hit_into_play:1,foul:1,foul_tip:1,swinging_strike:1,swinging_strike_blocked:1,foul_bunt:1,missed_bunt:1,bunt_foul_tip:1},WHIFF={swinging_strike:1,swinging_strike_blocked:1,missed_bunt:1}; var NONPA=/caught_stealing|pickoff|stolen_base|wild_pitch|passed_ball|other_advance|runner_double_play|game_advisory|defensive_indiff/,NONAB={walk:1,intent_walk:1,hit_by_pitch:1,sac_fly:1,sac_bunt:1,sac_fly_double_play:1,sac_bunt_double_play:1,catcher_interf:1,truncated_pa:1},HITB={single:1,double:2,triple:3,home_run:4}; function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return{'&':'&','<':'<','>':'>','"':'"'}[c]})} function F(v,f){if(v==null||!isFinite(v))return '—';return f==='p'?v.toFixed(1)+'%':f==='r'?(v>=1?v.toFixed(3):v.toFixed(3).replace(/^0/,'')):v.toFixed(1)} function parse(t){var rows=[],row=[],f='',q=false,c;for(var i=0;i5})}} function agg(rows,I){var a={n:0,sw:0,wh:0,pa:0,ab:0,hits:0,tb:0,wv:0,wd:0,xw:0,bbe:0,hh:0};rows.forEach(function(r){a.n++;var ds=r[I.description];SWING[ds]&&a.sw++;WHIFF[ds]&&a.wh++;var ev=r[I.events];if(ev&&!NONPA.test(ev)){a.pa++;if(!NONAB[ev]){a.ab++;if(HITB[ev]){a.hits++;a.tb+=HITB[ev]}}var wd=+r[I.woba_denom]||0,wv=parseFloat(r[I.woba_value])||0,bb=r[I.type]==='X';if(wd){a.wd+=wd;a.wv+=wv;var ew=parseFloat(r[I.estimated_woba_using_speedangle]);a.xw+=bb&&!isNaN(ew)?ew:wv}}if(r[I.type]==='X'){var ls=parseFloat(r[I.launch_speed]);if(!isNaN(ls)){a.bbe++;ls>=95&&a.hh++}}});return a} function cells(a){return ''+F(a.sw?a.wh/a.sw*100:null,'p')+''+a.pa+''+F(a.ab?a.hits/a.ab:null,'r')+''+F(a.ab?a.tb/a.ab:null,'r')+''+F(a.wd?a.wv/a.wd:null,'r')+''+F(a.wd?a.xw/a.wd:null,'r')+''+F(a.bbe?a.hh/a.bbe*100:null,'p')+''} var HEAD='Whiff%PAAVGSLGwOBAxwOBAHard-hit%'; var css='.btpv-ctl .btpv-tg{margin:0 0 0 6px;padding-left:12px;border-left:1px solid color-mix(in srgb,currentColor 18%,transparent);justify-content:flex-start}.btpv-tg{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin:4px 0 12px}.btpv-tg button{font:inherit;font-size:.82em;font-weight:700;color:inherit;background:none;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:999px;padding:5px 14px;cursor:pointer}.btpv-tg button.on{background:#3b82f6;border-color:#3b82f6;color:#fff}.btpv{margin:28px 0 8px;padding:16px;border:1px solid color-mix(in srgb,currentColor 12%,transparent);border-radius:14px;background:color-mix(in srgb,currentColor 4%,transparent)}.btpv h3{margin:0 0 4px;font-size:1.15em}.btpv>p{margin:0 0 12px;font-size:.85em;opacity:.7}.btpv-ctl{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-bottom:6px}.btpv select,.btpv input{font:inherit;font-size:.85em;font-weight:600;color:inherit;background:#0c2138;border:1px solid color-mix(in srgb,currentColor 15%,transparent);border-radius:8px;padding:6px 10px}.btpv button.go{font:inherit;font-size:.88em;font-weight:700;color:#fff;background:#3b82f6;border:0;border-radius:10px;padding:8px 14px;cursor:pointer}.btpv h4{font-size:.72em;letter-spacing:.08em;text-transform:uppercase;opacity:.75;margin:16px 0 6px}.btpv h4 span{display:inline-block;margin-left:8px;padding:1px 8px;border-radius:999px;border:1px solid color-mix(in srgb,currentColor 15%,transparent);text-transform:none;letter-spacing:.02em;font-size:1.05em;opacity:.95}.btpv-s{overflow-x:auto}.btpv table{width:100%;border-collapse:collapse;font-size:.85em;font-variant-numeric:tabular-nums;white-space:nowrap}.btpv th,.btpv td{padding:6px 8px;text-align:center;border-bottom:1px solid color-mix(in srgb,currentColor 10%,transparent)}.btpv th{font-size:.8em;opacity:.7}.btpv td:first-child{font-weight:700}.btpv svg{width:100%;height:auto;display:block}.btpv .note{font-size:.78em;opacity:.65;margin-top:10px}.btp>.btpv{margin:0 0 35.2px!important}'; var SIDE='';function lab(y1,y2){return ' '+(y1===y2?y1+' season':y1+'–'+y2+' seasons')+(SIDE?' \u00b7 '+SIDE:'')+''} function trend(d,I,y1,y2){var c={};d.rows.forEach(function(r){var t=r[I.pitch_type];(t==='FF'||t==='SI')&&(c[t]=(c[t]||0)+1)});var fb=(c.FF||0)>=(c.SI||0)?'FF':'SI';if(!c[fb])return '';var g={};d.rows.forEach(function(r){if(r[I.pitch_type]!==fb)return;var v=parseFloat(r[I.release_speed]);if(isNaN(v))return;var k=r[I.game_date];g[k]=g[k]||[0,0];g[k][0]+=v;g[k][1]++});var ks=Object.keys(g).sort();if(ks.length<2)return '';var pts=ks.map(function(k){return g[k][0]/g[k][1]}),mn=Math.floor(Math.min.apply(null,pts)-.5),mx=Math.ceil(Math.max.apply(null,pts)+.5),W=600,H=150,pl=34,pb=18,X=function(j){return pl+(W-pl-6)*j/(pts.length-1)},Y=function(v){return 6+(H-pb-6)*(1-(v-mn)/(mx-mn))},MY=ks[0].slice(0,4)!==ks[ks.length-1].slice(0,4),DL=function(k){return MY?(+k.slice(5,7))+'/'+(+k.slice(8,10))+'/'+k.slice(2,4):(+k.slice(5,7))+'/'+(+k.slice(8,10))},div='';if(MY)ks.forEach(function(k,j){if(j&&k.slice(0,4)!==ks[j-1].slice(0,4)){var xx=((X(j-1)+X(j))/2).toFixed(1);div+=''+k.slice(0,4)+''}}); return '

'+esc(PT[fb])+' velocity by game'+lab(y1,y2)+'

'+mx+''+mn+''+DL(ks[0])+''+DL(ks[ks.length-1])+''+div+''} function autoB(d,I){var c={};d.rows.forEach(function(r){var t=r[I.pitch_type],v=parseFloat(r[I.release_speed]);if((t==='FF'||t==='SI')&&v>50){c[t]=c[t]||[0,0];c[t][0]+=v;c[t][1]++}});var k=c.FF&&(!c.SI||c.FF[1]>=c.SI[1])?'FF':c.SI?'SI':null;if(!k||c[k][1]<20)return null;var a=c[k][0]/c[k][1],f=Math.round(a);window.__btFB=a;return [f-6,f-1,f+1]} function render(box,d,y1,y2,E){if(PIT)E=autoB(d,d.ix)||E;var key=PIT?'stand':'p_throws',opts=PIT?[['','All batters'],['R','vs RHB'],['L','vs LHB']]:[['','All pitchers'],['R','vs RHP'],['L','vs LHP']];var cur=box.getAttribute('data-side')||'';function draw(){box.setAttribute('data-side',cur);var I=d.ix,d2=cur?{ix:I,rows:d.rows.filter(function(r){return r[I[key]]===cur})}:d;SIDE=cur?opts.filter(function(o){return o[0]===cur})[0][1]:'';var tg='
'+opts.map(function(o){return ''}).join('')+'
';render0(box,d2,y1,y2,E);var ctl=box.parentNode&&box.parentNode.querySelector('.btpv-ctl')||box;var old=ctl.querySelector('.btpv-tg');if(old)old.remove();if(ctl===box)box.insertAdjacentHTML('afterbegin',tg);else ctl.insertAdjacentHTML('beforeend',tg);SIDE='';[].forEach.call(ctl.querySelectorAll('.btpv-tg button'),function(b){b.onclick=function(){cur=b.getAttribute('data-s');draw()}})}draw()} function render0(box,d,y1,y2,E){var I=d.ix;var out='',tot=d.rows.length;if(!tot){box.innerHTML='
No pitch data for '+lab(y1,y2).replace(/<[^>]+>/g,'')+'.
';return} var by={};d.rows.forEach(function(r){var t=r[I.pitch_type]||'?';(by[t]=by[t]||[]).push(r)});var types=Object.keys(by).filter(function(t){return t!=='?'&&by[t].length/tot>=.01}).sort(function(a,b){return by[b].length-by[a].length}); if(PIT){out+='

Pitch mix'+lab(y1,y2)+'

'+HEAD+''+types.map(function(t){var s=0,n=0,m=0;by[t].forEach(function(r){var v=parseFloat(r[I.release_speed]);if(!isNaN(v)){s+=v;n++;v>m&&(m=v)}});return ''+cells(agg(by[t],I))+''}).join('')+'
PitchUsageAvg veloMax
'+esc(PT[t]||t)+''+(by[t].length/tot*100).toFixed(1)+'%'+(n?(s/n).toFixed(1):'—')+''+(m?m.toFixed(1):'—')+'
'+trend(d,I,y1,y2)} else out+='

By pitch type faced'+lab(y1,y2)+'

'+HEAD+''+types.map(function(t){return ''+cells(agg(by[t],I))+''}).join('')+'
PitchSeen
'+esc(PT[t]||t)+''+(by[t].length/tot*100).toFixed(1)+'%
'; var B=[];for(var b=0;b<=E.length;b++)B.push([]);d.rows.forEach(function(r){var s=parseFloat(r[I.release_speed]);if(isNaN(s))return;var k=0;while(k=E[k])k++;B[k].push(r)}); var bl=function(i){return i===0?'Under '+E[0]:i===E.length?E[i-1]+'+':E[i-1]+'–'+(E[i]-.1).toFixed(1)}; out+='

'+(PIT?'Results allowed by pitch speed':'Results by pitch speed faced')+lab(y1,y2)+'

'+(PIT?'
Speed groups are set automatically from his usual fastball ('+(window.__btFB?window.__btFB.toFixed(1)+' mph average':'')+'): well below it, a bit below it, his usual range, and above it.
':'')+'
'+HEAD+''+B.map(function(rs,j){return ''+cells(agg(rs,I))+''}).join('')+'
Speed (mph)Pitches
'+bl(j)+''+rs.length+'
Regular season, every pitch from Baseball Savant. Whiff% = misses per swing; hard-hit = exit velocity 95 mph or more.
';box.innerHTML=out} function load(box,y1,y2,E){box.innerHTML='
Loading pitch data…
';var ys=[];for(var y=y1;y<=y2;y++)ys.push(y+'%7C');var who=PIT?'pitcher':'batter',k=who+y1+'-'+y2; (DATA[k]||(DATA[k]=(window.btSVX?btSVX(id,who,ys):Promise.resolve(null)).then(function(s){return s!=null?s:fetch('https://baseballsavant.mlb.com/statcast_search/csv?all=true&hfGT=R%7C&hfSea='+ys.join('')+'&player_type='+who+'&'+who+'s_lookup%5B%5D='+id+'&type=details').then(function(r){if(!r.ok)throw 0;return r.text()})}).then(parse))).then(function(d){render(box,d,y1,y2,E)}).catch(function(){delete DATA[k];box.innerHTML='
Baseball Savant didn’t respond. Try again in a moment.
'})} function mount(){if(document.getElementById('btpv'))return;fetch('https://statsapi.mlb.com/api/v1/people/'+id+'?hydrate=stats(group=[hitting,pitching],type=[yearByYear])').then(function(r){return r.json()}).then(function(j){var p=(j.people||[])[0]||{};PIT=!!(p.primaryPosition&&p.primaryPosition.type==='Pitcher');var yrs={};(p.stats||[]).forEach(function(s){if(s.group&&s.group.displayName!==(PIT?'pitching':'hitting'))return;(s.splits||[]).forEach(function(x){if(x.sport&&x.sport.id===1&&+x.season>=MINY)yrs[+x.season]=1})});var list=Object.keys(yrs).map(Number).sort(function(a,b){return b-a});if(!list.length)return; var st=document.createElement('style');st.textContent=css;document.head.appendChild(st);var sec=document.createElement('section');sec.id='btpv';sec.className='btpv';var opt=list.map(function(y){return ''}).join(''); sec.innerHTML='

'+(PIT?'Pitch mix & velocity':'Pitch types & velocity')+'

'+(PIT?'Usage, velocity and results by pitch, game-by-game fastball velocity, and results allowed by pitch speed.':'How he does against each pitch type and each pitch-speed band.')+' MLB regular season.

to
'; var place=function(){var st=host.querySelector('.btp-stats');if(st&&st.parentNode){if(sec.previousElementSibling!==st)st.parentNode.insertBefore(sec,st.nextSibling)}else if(!sec.parentNode)host.parentNode.insertBefore(sec,host.nextSibling)};place();var pt=0;new MutationObserver(function(){if(pt)return;pt=setTimeout(function(){pt=0;place()},200)}).observe(host,{childList:true,subtree:true});var s1=sec.querySelector('[data-y1]'),s2=sec.querySelector('[data-y2]'),bi=sec.querySelector('[data-b]'),out=sec.querySelector('.btpv-out'),go=sec.querySelector('.go');s1.value=list[0];s2.value=list[0]; function E(){var e=bi.value.split(',').map(parseFloat).filter(function(x){return x>60&&x<110}).sort(function(a,b){return a-b});return e.length?e:[90,93,96]} function run(){var a=+s1.value,b=+s2.value;if(b