RRosternomics

Yankees NYY

GM: Brian Cashman

Overview

FORCED-SPEND ↘ cooling
expensive & losing · year 1 in this regime · payroll 2.3× league · TAB -15.2
-15.2TAB — talent above budget29th/30
-7.4WAB — realized wins (TAB + luck)22nd/30
7.0depth — 2+ WAR regulars13th/30
-5.7dead WAR — sub-replacement drag17th/30
38%dead money %19th/30
0.525win %14th/30
29.0roster fWAR19th/30
-161surplus $M (value vs cost)30th/30
$239Mpayroll $M1st/30

Roster

CFBrett Gardner 138GCurtis Granderson 25GIchiro Suzuki 13G
LFAlfonso Soriano 134GVernon Wells 73GZoilo Almonte 25G
RFIchiro Suzuki 128GVernon Wells 23GBrennan Boesch 15G
SSBrendan Ryan 101GEduardo Núñez 75GJayson Nix 48G
2BRobinson Canó 153GDavid Adams 9GJayson Nix 4G
3BMark Reynolds 54GLuis Alfonso Cruz 41GJayson Nix 41G
1BLyle Overbay 130GMark Reynolds 65GMark Teixeira 14G
STARTERSHiroki Kuroda 3.2WARAndy Pettitte 2.7WARIván Nova 2.1WARCC Sabathia 2.1WARPhil Hughes 1.0WAR
CChris Stewart 108GAustin Romine 59GFrancisco Cervelli 17G
DHTravis Hafner 72GVernon Wells 22GAlex Rodriguez 16G
BULLPEN Mariano Rivera 44svDavid Robertson 33hBoone Logan 11hShawn Kelley 11hJoba Chamberlain 5hPreston Claiborne 4hAdam Warren 1hDavid Huff 0h
CChris Stewart 108 · Austin Romine 59 · Francisco Cervelli 17
1BLyle Overbay 130 · Mark Reynolds 65 · Mark Teixeira 14
2BRobinson Canó 153 · David Adams 9 · Jayson Nix 4
3BMark Reynolds 54 · Luis Alfonso Cruz 41 · Jayson Nix 41
SSBrendan Ryan 101 · Eduardo Núñez 75 · Jayson Nix 48
LFAlfonso Soriano 134 · Vernon Wells 73 · Zoilo Almonte 25
CFBrett Gardner 138 · Curtis Granderson 25 · Ichiro Suzuki 13
RFIchiro Suzuki 128 · Vernon Wells 23 · Brennan Boesch 15
DHTravis Hafner 72 · Vernon Wells 22 · Alex Rodriguez 16
SPHiroki Kuroda 3.2 · Andy Pettitte 2.7 · Iván Nova 2.1 · CC Sabathia 2.1 · Phil Hughes 1.0
BP Mariano Rivera 44 · David Robertson 33 · Boone Logan 11 · Shawn Kelley 11 · Joba Chamberlain 5 · Preston Claiborne 4 · Adam Warren 1 · David Huff 0

Each position lists its starter then backups by games (so platoons & bench show). Starters (mound) & bullpen (left, foul territory) are ranked by WAR; marks the closer (most save chances). MLB Stats API.

2013 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Robinson Canó 30 FA-elig 1605.9 15.0 +0.8
Brett Gardner 29 arb 1453.3 2.9 +5.9
Hiroki Kuroda 38 arb 323.2 15.0 -6.4
Andy Pettitte 41 FA-elig 302.7 12.0 -4.8
Chris Stewart 31 FA-elig 1092.4 0.5 +6.0
CC Sabathia 32 FA-elig 322.1 23.0 -17.4
Iván Nova 26 arb 232.1 0.6 +5.0
Alfonso Soriano↔ CHC 37 FA-elig 581.8 6.9 -2.1
David Robertson 28 arb 701.4 3.1 +0.6
Mariano Rivera 43 FA-elig 641.3 10.0 -6.5
Curtis Granderson 32 FA-elig 611.2 15.0 -11.7
Ichiro Suzuki 39 FA-elig 1501.1 6.5 -3.6
Francisco Cervelli 27 arb 171.0 0.5 +2.2
Phil Hughes 27 FA-elig 301.0 7.2 -4.5
Jayson Nix 30 arb 870.9 0.9 +1.4
David Phelps 26 pre-arb 220.8 0.5 +1.7
Alex Rodriguez 37 FA-elig 440.6 28.0 -26.3
Mark Reynolds↔ CLE 29 FA-elig 360.4 1.6 -0.4
Shawn Kelley 29 arb 570.4 0.9 +0.1
Melky Mesa 26 pre-arb 50.2 0.0 +0.6
Vidal Nuño 25 50.2 0.2 +0.4
Boone Logan 28 FA-elig 610.1 3.1 -2.7
John Ryan Murphy 22 160.1 0.1 +0.3
Matt Daley 31 70.1
Cesar Cabral 24 pre-arb 80.1 0.5 -0.2
Preston Claiborne 25 440.1 0.4 -0.2
Dellin Betances 25 pre-arb 60.0 0.1 -0.0
Cody Eppley 27 pre-arb 20.0 0.5 -0.4
Chris Nelson↔ ANA 27 arb 100.0 0.1 +0.0
Brendan Ryan↔ SEA 31 FA-elig 170.0 0.5 -0.5
Corban Joseph 24 2-0.0
Adam Warren 25 pre-arb 34-0.0 0.5 -0.5
Mike Zagurski↔ PIT 30 1-0.0
Chris Bootcheck 34 1-0.0
Travis Ishikawa↔ BAL 29 1-0.0 0.0 -0.1
Jim Miller 31 pre-arb 1-0.1 0.0 -0.3
David Adams 26 pre-arb 43-0.1 0.3 -0.5
David Huff↔ CLE 28 arb 11-0.1 0.4 -0.7
Lyle Overbay 36 FA-elig 142-0.1 1.2 -1.6
Mark Teixeira 33 FA-elig 15-0.1 22.5 -22.9
Brennan Boesch 28 arb 23-0.2 1.5 -1.9
Austin Romine 24 pre-arb 60-0.2 0.4 -0.9
Travis Hafner 36 FA-elig 82-0.2 2.0 -2.4
Luis Alfonso Cruz↔ LAD 29 arb 16-0.2 0.1 -0.6
Thomas Neal↔ CHC 25 4-0.2
Zoilo Almonte 24 pre-arb 34-0.2 0.3 -0.9
Brett Marshall 23 pre-arb 3-0.2 0.1 -0.8
Alberto Gonzalez↔ CHC 30 arb 13-0.3 0.1 -0.7
Eduardo Núñez 26 arb 90-0.3 0.5 -1.4
Vernon Wells 34 FA-elig 130-0.3 21.0 -21.9
Kevin Youkilis 34 FA-elig 28-0.3 12.0 -12.9
Brent Lillibridge↔ CHC 29 arb 11-0.3 0.4 -1.3
Ben Francisco 31 FA-elig 21-0.5 1.1 -2.4
Derek Jeter 39 FA-elig 17-0.5 17.0 -18.3
Reid Brignac↔ COL 27 arb 17-0.5 0.2 -1.5
Joba Chamberlain 27 FA-elig 45-0.7 1.9 -3.7

Key additions (new vs 2012)

PlayerfWAR
Alfonso Soriano1.8
Mark Reynolds0.4
Shawn Kelley0.4
Vidal Nuño0.2
John Ryan Murphy0.1
Matt Daley0.1

Key subtractions (gone from 2012)

Player2012 fWAR
Russell Martin4.0
Nick Swisher3.6
Eric Chavez1.6
Freddy Garcia0.6
Raul Ibanez0.5
Dewayne Wise0.5

Current & Historical Payroll

2013 payroll
$239.1M

What the front office was committed to as of 2013, 2013–2019. Pre-2015 has no contract-terms data — forward italic $ are realized actuals (hindsight, not commitments known then). Control labels (ARB/FA) are point-in-time.

current $ guaranteed $ actual $ (hindsight) arbitration (proj) pre-arb (proj) free agent

Guaranteed / signed (15)

PlayerAgeSvc2013201420152016201720182019
Alex Rodriguez3718.011$28.0MFREE AGENT$22.0M$21.0M
CC Sabathia3212.000$23.0M$23.0M$23.0M$25.0M$25.0M$10.0M$8.0M
Mark Teixeira3310.000$22.5M$22.5M$23.1M$23.1MFREE AGENT
Vernon Wells3411.113$21.0MFREE AGENT
Derek Jeter3917.042$17.0M$12.0MFREE AGENT
Curtis Granderson3214.077$15.0MFREE AGENT
Robinson Canó307.153$15.0MFREE AGENT
Kevin Youkilis348.093$12.0MFREE AGENT
Andy Pettitte4116.144$12.0MFREE AGENT
Mariano Rivera4317.105$10.0MFREE AGENT
Alfonso Soriano3712.079$6.9M$18.0MFREE AGENT
Ichiro Suzuki3912.000$6.5M$6.5MFREE AGENT
Travis Hafner3610.009$2.0MFREE AGENT
Lyle Overbay3610.009$1.2MFREE AGENT
Chris Bootcheck3410.000FREE AGENT

Arbitration-eligible (18)

PlayerAgeSvc2013201420152016201720182019
Hiroki Kuroda385.000$15.0MFREE AGENT
Phil Hughes275.113$7.2MFREE AGENT
Boone Logan285.140$3.1MFREE AGENT
David Robertson284.070$3.1MARB 3FREE AGENT
Brett Gardner294.072$2.9MARB 3FREE AGENT
Joba Chamberlain275.055$1.9MFREE AGENT
Mark Reynolds295.138$1.6MFREE AGENT
Brennan Boesch283.000$1.5MARB 2ARB 3FREE AGENT
Ben Francisco315.049$1.1MFREE AGENT
Shawn Kelley293.128$935kARB 2ARB 3FREE AGENT
Jayson Nix303.127$900kARB 2ARB 3FREE AGENT
Brendan Ryan315.082$531kFREE AGENT
Brent Lillibridge293.115$412kARB 2ARB 3FREE AGENT
Luis Alfonso Cruz295.000$132kFREE AGENT
Alberto Gonzalez303.135$54kARB 2ARB 3FREE AGENT
Travis Ishikawa293.072$4kARB 2ARB 3FREE AGENT
Matt Daley314.000ARB 3FREE AGENT
Mike Zagurski303.021ARB 2ARB 3FREE AGENT

Pre-arbitration (23)

PlayerAgeSvc2013201420152016201720182019
Iván Nova262.035$576kARB 1ARB 2ARB 3FREE AGENT
Eduardo Núñez261.117$533kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Francisco Cervelli272.146$515kARB 1ARB 2ARB 3FREE AGENT
Chris Stewart312.091$515kARB 1ARB 2ARB 3FREE AGENT
David Phelps260.156$512kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Cody Eppley272.000$510kARB 1ARB 2ARB 3FREE AGENT
Adam Warren250.036$491kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Cesar Cabral241.000$490kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Austin Romine240.159$418kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
David Huff281.166$388kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Preston Claiborne250.000$372kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Zoilo Almonte240.104$278kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
David Adams260.096$254kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Reid Brignac272.120$184kARB 1ARB 2ARB 3FREE AGENT
Vidal Nuño250.000$153kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Dellin Betances250.035$104kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Brett Marshall230.033$88kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
John Ryan Murphy220.000$80kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Chris Nelson272.171$77kARB 1ARB 2ARB 3FREE AGENT
Melky Mesa260.037$35kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jim Miller311.010$19kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Thomas Neal25
Corban Joseph240.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Yankees 2013; the black line = the league-average player at each slot that season. Staying above the line deep into the roster is the depth edge; dropping below it = holes & dead weight.

What each regime is worth — every settled team-season since 1985

RegimeSeasonsWin %Playoff %Pennant %WS %
WINDOW 28952.230.14.81.0
CONTEND 31156.863.019.310.3
FORCED-SPEND 26047.98.81.21.2
REBUILD 32843.31.20.30.3
const FONT={family:"'Roboto Mono', monospace",size:12}; const ORBIT = [[1985, 1.352, 12.53], [1986, 1.402, 5.33], [1987, 1.533, -3.16], [1988, 1.649, -8.48], [1989, 1.029, -2.54], [1990, 1.173, -15.29], [1991, 1.143, -10.09], [1992, 1.109, 0.62], [1993, 1.381, 6.52], [1994, 1.436, 12.5], [1995, 1.58, -0.14], [1996, 1.512, 2.43], [1997, 1.605, 6.43], [1998, 1.588, 17.45], [1999, 1.803, 5.82], [2000, 1.888, -3.47], [2001, 1.882, 2.14], [2002, 2.002, 13.92], [2003, 2.112, 11.88], [2004, 2.76, -6.07], [2005, 2.884, -14.29], [2006, 2.544, -3.91], [2007, 2.523, 2.09], [2008, 2.484, -6.98], [2009, 2.281, 9.29], [2010, 2.38, 3.28], [2011, 2.079, 11.29], [2012, 2.121, 7.41], [2013, 2.304, -15.17], [2014, 2.004, -7.44], [2015, 1.794, -2.14], [2016, 1.742, -7.29], [2017, 1.43, 13.9], [2018, 1.224, 16.54], [2019, 1.67, 8.1], [2021, 1.736, -0.91], [2022, 1.725, 14.19], [2023, 1.693, -12.33], [2024, 1.823, 7.16], [2025, 1.578, 10.87], [2026, 1.702, 6.42]], TEAMCOL = '#1A2B5E'; function contrastColor(hex,dark){let r=parseInt(hex.slice(1,3),16)/255,g=parseInt(hex.slice(3,5),16)/255,b=parseInt(hex.slice(5,7),16)/255; const mx=Math.max(r,g,b),mn=Math.min(r,g,b);let h=0,s=0,l=(mx+mn)/2; if(mx!==mn){const d=mx-mn;s=l>0.5?d/(2-mx-mn):d/(mx+mn);h=mx===r?(g-b)/d+(g1)t-=1;if(t<1/6)return p+(q-p)*6*t;if(t<0.5)return q;if(t<2/3)return p+(q-p)*(2/3-t)*6;return p;} let R,G,B;if(s===0){R=G=B=L;}else{const q=L<0.5?L*(1+s):L+s-L*s,p=2*L-q;R=h2(p,q,h+1/3);G=h2(p,q,h);B=h2(p,q,h-1/3);} const x=v=>('0'+Math.round(v*255).toString(16)).slice(-2);return '#'+x(R)+x(G)+x(B);} const CURRENT_YEAR = 2013; function drawOrbit(){ const orbEl=document.getElementById('orbit'); if(!ORBIT || ORBIT.length<2){ orbEl.innerHTML='

Not enough history to chart.

'; return; } const f=+document.getElementById('yfrom').value, t=+document.getElementById('yto').value, w=+document.getElementById('win').value; document.getElementById('yflab').textContent=f; document.getElementById('ytlab').textContent=t; document.getElementById('winlab').textContent = w===1?'raw':(w+'-yr'); const dark=document.documentElement.getAttribute('data-theme')==='dark'; const ink=dark?'#cdd4dc':'#1d2630', grid=dark?'#2c313a':'#d6dbe2', refc=dark?'#5a636d':'#9aa3ad', dirc=dark?'#ffffff':'#1d2630'; const col=contrastColor(TEAMCOL,dark); function sm(a,w){const h=(w-1)/2,o=[];for(let i=0;id[0]>=f&&d[0]<=t); if(raw.length<2){ Plotly.purge('orbit'); orbEl.innerHTML='

Pick a wider year range.

'; return; } const s=sm(raw,w),n=s.length,X=s.map(d=>Math.round(d[1]*100)/100),Y=s.map(d=>Math.round(d[2]*10)/10),yr=s.map(d=>d[0]); let xa=Math.min(1,...X),xb=Math.max(1,...X),ya=Math.min(0,...Y),yb=Math.max(0,...Y); const px=(xb-xa)*.12+.04,py=(yb-ya)*.12+1.5;xa-=px;xb+=px;ya-=py;yb+=py; const k=Math.max(0,n-3), ann=[]; [['WINDOW','#15b6a0',xa<1&&yb>0,xa,yb,'left','top'],['CONTEND','#2f7ed8',xb>1&&yb>0,xb,yb,'right','top'], ['FORCED-SPEND','#ec5a3c',xb>1&&ya<0,xb,ya,'right','bottom'],['REBUILD','#f0a91c',xa<1&&ya<0,xa,ya,'left','bottom']].forEach(q=>{ if(q[2]) ann.push({x:q[3],y:q[4],xref:'x',yref:'y',text:q[0],showarrow:false,xanchor:q[5],yanchor:q[6],font:{size:10,color:q[1]},opacity:.85});}); ann.push({x:X[n-1],y:Y[n-1],ax:X[n-1]-0.5*(X[n-1]-X[k]),ay:Y[n-1]-0.5*(Y[n-1]-Y[k]),xref:'x',yref:'y',axref:'x',ayref:'y',showarrow:true,arrowhead:2,arrowsize:1.3,arrowwidth:1.7,arrowcolor:dirc,text:''}); Plotly.newPlot('orbit',[{x:X,y:Y,mode:'lines+markers',line:{color:col,width:2.6,shape:'spline',smoothing:0.7}, marker:{color:col,size:s.map((d,i)=>5+7*(i/Math.max(1,n-1))),line:{color:dark?'#1b1e23':'#fff',width:0.6}}, text:yr,hovertemplate:'%{text}
payroll %{x:.2f}× lg · TAB %{y:+.1f}
'+(w===1?'single season (raw)':w+'-yr smoothed avg')+''}], {margin:{l:50,r:14,t:10,b:40},paper_bgcolor:'rgba(0,0,0,0)',plot_bgcolor:'rgba(0,0,0,0)',font:FONT, xaxis:{title:{text:'relative payroll (× lg avg)',font:{size:11}},range:[xa,xb],gridcolor:grid,zeroline:false}, yaxis:{title:{text:'TAB (talent above budget)',font:{size:11}},range:[ya,yb],gridcolor:grid,zeroline:false}, shapes:[{type:'line',x0:1,x1:1,y0:ya,y1:yb,line:{color:refc,width:1,dash:'dot'}},{type:'line',x0:xa,x1:xb,y0:0,y1:0,line:{color:refc,width:1,dash:'dot'}}], annotations:ann},{displayModeBar:false,responsive:true}); } (function initOrbitSliders(){ const yf=document.getElementById('yfrom'), yt=document.getElementById('yto'); if(ORBIT && ORBIT.length>=2){ const yrs=ORBIT.map(d=>d[0]), y0=Math.min(...yrs), y1=Math.max(...yrs); yf.min=y0; yf.max=y1; yf.value=y0; yt.min=y0; yt.max=y1; yt.value=Math.max(y0, Math.min(CURRENT_YEAR, y1)); yf.addEventListener('input',()=>{ if(+yf.value>+yt.value) yt.value=yf.value; drawOrbit(); }); yt.addEventListener('input',()=>{ if(+yt.value<+yf.value) yf.value=yt.value; drawOrbit(); }); document.getElementById('win').addEventListener('input',drawOrbit); } // only paint on load if the orbit pane is the visible default (else it'd draw into a 0-width hidden box) const _op=document.querySelector('.gpane[data-g="orbit"]'); if(!_op || _op.style.display!=='none') drawOrbit(); })(); new MutationObserver(()=>{ const p=document.querySelector('.gpane[data-g="orbit"]'); if(!p || p.style.display!=='none') drawOrbit(); }).observe(document.documentElement,{attributes:true,attributeFilter:['data-theme']}); // ---- roster depth curve (WAR by roster slot vs league-average-by-slot) ---- const WARDIST = {"cats": ["IF", "OF", "SP", "SP", "C", "SP", "SP", "OF", "RP", "RP", "OF", "OF", "C", "SP", "IF", "SP", "IF", "IF", "RP", "OF", "SP", "RP", "C", "RP", "RP", "RP", "RP", "RP", "IF", "IF", "IF", "RP", "RP", "RP", "IF", "RP", "IF", "RP", "IF", "IF"], "league": [5.83, 4.29, 3.79, 2.96, 2.6, 2.33, 2.15, 1.94, 1.7, 1.5, 1.38, 1.2, 1.04, 0.94, 0.82, 0.72, 0.65, 0.53, 0.47, 0.39, 0.33, 0.26, 0.22, 0.17, 0.14, 0.1, 0.07, 0.04, 0.01, -0.01, -0.05, -0.07, -0.09, -0.11, -0.14, -0.16, -0.21, -0.23, -0.28, -0.29], "names": ["Robinson Can\u00f3", "Brett Gardner", "Hiroki Kuroda", "Andy Pettitte", "Chris Stewart", "CC Sabathia", "Iv\u00e1n Nova", "Alfonso Soriano", "David Robertson", "Mariano Rivera", "Curtis Granderson", "Ichiro Suzuki", "Francisco Cervelli", "Phil Hughes", "Jayson Nix", "David Phelps", "Alex Rodriguez", "Mark Reynolds", "Shawn Kelley", "Melky Mesa", "Vidal Nu\u00f1o", "Boone Logan", "John Ryan Murphy", "Matt Daley", "Cesar Cabral", "Preston Claiborne", "Dellin Betances", "Cody Eppley", "Chris Nelson", "Brendan Ryan", "Corban Joseph", "Adam Warren", "Mike Zagurski", "Chris Bootcheck", "Travis Ishikawa", "Jim Miller", "David Adams", "David Huff", "Lyle Overbay", "Mark Teixeira"], "ranks": [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], "team": [5.89, 3.26, 3.22, 2.69, 2.43, 2.1, 2.08, 1.79, 1.38, 1.31, 1.23, 1.09, 0.99, 0.99, 0.85, 0.84, 0.62, 0.44, 0.37, 0.25, 0.19, 0.15, 0.15, 0.14, 0.09, 0.08, 0.04, 0.04, 0.03, 0.02, -0.0, -0.01, -0.02, -0.02, -0.04, -0.09, -0.1, -0.11, -0.13, -0.13]}; let WD_MODE='raw', WC_MODE='team'; const POSCOL={SP:'#2f7ed8',RP:'#6fb1ec',IF:'#f0a91c',OF:'#15b6a0',C:'#ec5a3c'}; const POSLAB={SP:'Starting P',RP:'Bullpen',IF:'Infield',OF:'Outfield',C:'Catcher'}; function buildPosLegend(){ const el=document.getElementById('poslegend'); if(!el) return; el.style.display=WC_MODE==='pos'?'flex':'none'; el.innerHTML=['SP','RP','C','IF','OF'].map(k=>''+POSLAB[k]+'').join(''); } function drawWarDist(){ if(!WARDIST) return; const dark=document.documentElement.getAttribute('data-theme')==='dark'; const col=contrastColor(TEAMCOL,dark), line=dark?'#e8eef0':'#111', grid=dark?'#2c313a':'#d6dbe2', ink=dark?'#cdd4dc':'#1d2630'; let team=WARDIST.team.slice(), league=WARDIST.league.slice(); if(WD_MODE==='prop'){ const ts=team.reduce((a,b)=>a+Math.max(b,0),0)||1, ls=league.reduce((a,b)=>a+Math.max(b,0),0)||1; team=team.map(v=>v/ts); league=league.map(v=>v/ls); } const fmt=WD_MODE==='prop'?'.1%':'.1f'; const names=WARDIST.names||[], cats=WARDIST.cats||[]; const cd=WARDIST.ranks.map((_,i)=>[names[i]||'(empty slot)', WARDIST.team[i], POSLAB[cats[i]]||'']); const barcol=WC_MODE==='pos'?cats.map(c=>POSCOL[c]||'#9aa3ad'):col; const posbit=WC_MODE==='pos'?' · %{customdata[2]}':''; const htmpl=WD_MODE==='prop' ? '%{customdata[0]}'+posbit+'
slot %{x} · %{customdata[1]:.1f} WAR
%{y:.1%} of roster WAR' : '%{customdata[0]}'+posbit+'
slot %{x} · %{y:.1f} WAR'; buildPosLegend(); const _div=document.getElementById('wardist'); const traces=[ {type:'bar',x:WARDIST.ranks,y:team,name:'NYY',marker:{color:barcol},customdata:cd,hovertemplate:htmpl}, {type:'scatter',x:WARDIST.ranks,y:league,name:'league avg',mode:'lines',line:{color:line,width:2,shape:'spline',smoothing:0.6},hovertemplate:'league slot %{x}: %{y:'+fmt+'}'} ]; const layout={margin:{l:48,r:12,t:6,b:38},paper_bgcolor:'rgba(0,0,0,0)',plot_bgcolor:'rgba(0,0,0,0)',font:FONT, transition:{duration:360,easing:'cubic-in-out'}, xaxis:{title:{text:'roster slot (ranked by WAR)',font:{size:11}},gridcolor:grid,zeroline:false,color:ink}, yaxis:{title:{text:WD_MODE==='prop'?'share of roster WAR':'WAR',font:{size:11}},gridcolor:grid,zeroline:true,zerolinecolor:grid,color:ink,tickformat:WD_MODE==='prop'?'.0%':undefined}, showlegend:true,legend:{orientation:'h',y:1.13,x:0,font:{size:11}},bargap:0.14}; if(_div.data&&_div.data.length){ Plotly.react(_div,traces,layout,{displayModeBar:false,responsive:true}); } else { Plotly.newPlot(_div,traces,layout,{displayModeBar:false,responsive:true}); } } window.wdMode=function(m){WD_MODE=m;document.getElementById('wd-raw').classList.toggle('on',m==='raw');document.getElementById('wd-prop').classList.toggle('on',m==='prop');drawWarDist();}; window.wcToggle=function(){WC_MODE=WC_MODE==='pos'?'team':'pos';const b=document.getElementById('wc-btn');b.classList.toggle('on',WC_MODE==='pos');b.textContent=WC_MODE==='pos'?'Show team color':'Color by position';drawWarDist();}; // ---- Graphs dropdown switcher (orbit ⇄ roster value distribution) ---- let _wdRendered=false; window.showTeamGraph=function(g){ document.querySelectorAll('.gpane').forEach(p=>{ p.style.display=(p.dataset.g===g)?'':'none'; }); if(g==='orbit'){ drawOrbit(); } else if(g==='roster'){ if(!_wdRendered){ drawWarDist(); _wdRendered=true; } else { try{ Plotly.Plots.resize('wardist'); }catch(e){ drawWarDist(); } } } }; { const _ts=document.getElementById('tgsel'); if(_ts) _ts.addEventListener('change',()=>showTeamGraph(_ts.value)); } // initial paint of the default pane (roster when the team has roster data, else orbit already drawn above) if(WARDIST){ drawWarDist(); _wdRendered=true; } // redraw the roster pane on theme change only if it's been rendered and is visible if(WARDIST){ new MutationObserver(()=>{ const p=document.querySelector('.gpane[data-g="roster"]'); if(_wdRendered && p && p.style.display!=='none') drawWarDist(); }).observe(document.documentElement,{attributes:true,attributeFilter:['data-theme']}); } // ---- TOC scroll-spy ---- (function(){ const links=[...document.querySelectorAll('.toc a')]; const secs=links.map(a=>document.querySelector(a.getAttribute('href'))).filter(Boolean); links.forEach(a=>a.addEventListener('click',e=>{e.preventDefault();document.querySelector(a.getAttribute('href')).scrollIntoView({behavior:'smooth'});})); if('IntersectionObserver' in window && secs.length){ const obs=new IntersectionObserver(es=>{es.forEach(e=>{if(e.isIntersecting) links.forEach(l=>l.classList.toggle('on',l.getAttribute('href')==='#'+e.target.id));});},{rootMargin:'-15% 0px -75% 0px'}); secs.forEach(s=>obs.observe(s)); } })(); // ---- sortable roster table ---- const SVC_RANK={'pre-arb':0,'arb':1,'FA-elig':2,'?':3}; function cellNum(td){const n=parseFloat(td.textContent.replace(/[^0-9.\-]/g,''));return isNaN(n)?null:n;} function scale(v,mn,mx,g){ if(g==='diverge'){ if(v>=0){const t=mx>0?v/mx:0;return `rgba(26,125,90,${(0.10+0.55*t).toFixed(2)})`;} const t=mn<0?v/mn:0;return `rgba(192,73,43,${(0.10+0.55*t).toFixed(2)})`; } if(g==='salmag'){const t=mx>mn?(v-mn)/(mx-mn):0;return `rgba(128,135,143,${(0.05+0.45*t).toFixed(2)})`;} const t=mx>mn?(v-mn)/(mx-mn):0;return `rgba(26,125,90,${(0.05+0.55*t).toFixed(2)})`; } function applyGradients(table){ [...table.tHead.rows[0].cells].forEach((th,ci)=>{ if(!th.dataset.grad) return; const rows=[...table.tBodies[0].rows], vals=rows.map(r=>cellNum(r.cells[ci])).filter(v=>v!==null); if(!vals.length) return; const mn=Math.min(...vals),mx=Math.max(...vals); rows.forEach(r=>{const v=cellNum(r.cells[ci]);if(v!==null)r.cells[ci].style.background=scale(v,mn,mx,th.dataset.grad);}); }); } function makeSortable(table){ const ths=[...table.tHead.rows[0].cells]; ths.forEach((th,ci)=>{ th.dataset.label=th.textContent.trim(); th.addEventListener('click',()=>{ th._dir=(th._dir===-1)?1:-1; const dir=th._dir, tb=table.tBodies[0], svc=th.dataset.sortType==='service'; [...tb.rows].sort((a,b)=>{ let va,vb; if(svc){va=SVC_RANK[a.cells[ci].textContent.trim()]??9;vb=SVC_RANK[b.cells[ci].textContent.trim()]??9;} else{va=cellNum(a.cells[ci]);vb=cellNum(b.cells[ci]); if(va===null||vb===null){va=a.cells[ci].textContent.trim().toLowerCase();vb=b.cells[ci].textContent.trim().toLowerCase();}} return vavb?dir:0; }).forEach(r=>tb.appendChild(r)); ths.forEach(h=>h.innerHTML=h.dataset.label); th.innerHTML=th.dataset.label+' '+(dir===1?'▲':'▼')+''; }); }); } document.querySelectorAll('table.sortable').forEach(t=>{applyGradients(t);makeSortable(t);});