RRosternomics

Angels ANA

GM: Tony Reagins

Overview

CONTEND → steady
expensive & winning · year 1 in this regime · payroll 1.31× league · TAB +5.3
+5.3TAB — talent above budget8th/30
+11.7WAB — realized wins (TAB + luck)3rd/30
11.0depth — 2+ WAR regulars3rd/30
-4.0dead WAR — sub-replacement drag10th/30
51%dead money %23rd/30
0.599win %2nd/30
42.8roster fWAR5th/30
-18surplus $M (value vs cost)20th/30
$120Mpayroll $M6th/30

2009: made playoffs · 97–65. Ranks are vs the 30 teams that season.

Roster

CFTorii Hunter 115GGary Matthews Jr. 56GReggie Willits 6G
LFJuan Rivera 124GReggie Willits 27GRobb Quinlan 17G
RFBobby Abreu 126GGary Matthews Jr. 28GReggie Willits 10G
SSErick Aybar 136GMaicer Izturis 28GBrandon Wood 5G
2BHowie Kendrick 95GMaicer Izturis 68GSean Rodríguez 5G
3BChone Figgins 154GRobb Quinlan 9GBrandon Wood 9G
1BKendrys Morales 152GRobb Quinlan 17GBrandon Wood 4G
STARTERSJered Weaver 3.8WARJohn Lackey 3.2WARScott Kazmir 1.3WARJoe Saunders 1.0WARErvin Santana 0.8WAR
CMike Napoli 96GJeff Mathis 79GBobby Wilson 11G
DHVladimir Guerrero 93GMike Napoli 18GBobby Abreu 14G
BULLPEN Brian Fuentes 48svDarren Oliver 20hKevin Jepsen 17hJose Arredondo 16hJason Bulger 9hJustin Speier 8hScot Shields 6hRafael Rodriguez 2h
CMike Napoli 96 · Jeff Mathis 79 · Bobby Wilson 11
1BKendrys Morales 152 · Robb Quinlan 17 · Brandon Wood 4
2BHowie Kendrick 95 · Maicer Izturis 68 · Sean Rodríguez 5
3BChone Figgins 154 · Robb Quinlan 9 · Brandon Wood 9
SSErick Aybar 136 · Maicer Izturis 28 · Brandon Wood 5
LFJuan Rivera 124 · Reggie Willits 27 · Robb Quinlan 17
CFTorii Hunter 115 · Gary Matthews Jr. 56 · Reggie Willits 6
RFBobby Abreu 126 · Gary Matthews Jr. 28 · Reggie Willits 10
DHVladimir Guerrero 93 · Mike Napoli 18 · Bobby Abreu 14
SPJered Weaver 3.8 · John Lackey 3.2 · Scott Kazmir 1.3 · Joe Saunders 1.0 · Ervin Santana 0.8
BP Brian Fuentes 48 · Darren Oliver 20 · Kevin Jepsen 17 · Jose Arredondo 16 · Jason Bulger 9 · Justin Speier 8 · Scot Shields 6 · Rafael Rodriguez 2

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.

2009 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Chone Figgins 31 FA-elig 1586.6 5.8 +9.9
Torii Hunter 33 FA-elig 1193.8 18.0 -8.8
Jered Weaver 26 arb 333.8 0.5 +8.7
Erick Aybar 25 arb 1373.8 0.5 +8.5
Kendrys Morales 26 arb 1523.7 1.1 +7.7
Maicer Izturis 28 arb 1143.2 1.6 +6.1
John Lackey 30 FA-elig 273.2 10.0 -2.3
Bobby Abreu 35 FA-elig 1522.7 5.0 +1.5
Juan Rivera 30 FA-elig 1382.6 3.2 +2.9
Mike Napoli 27 arb 1142.3 2.0 +3.4
Howie Kendrick 25 arb 1052.1 0.5 +4.6
Scott Kazmir↔ TBR 25 arb 61.3 1.4 +1.6
Darren Oliver 38 FA-elig 631.2 3.7 -0.8
Kevin Jepsen 24 pre-arb 541.1 0.4 +2.3
Joe Saunders 28 arb 311.0 0.5 +1.8
Ervin Santana 26 arb 240.8 3.8 -1.8
Jeff Mathis 26 arb 840.6 0.5 +1.1
Matt Palmer 30 400.6
Jason Bulger 30 arb 640.6 0.4 +1.0
Vladimir Guerrero 34 FA-elig 1000.5 15.0 -13.8
Shane Loux 29 FA-elig 180.5 0.4 +0.7
Brian Fuentes 33 FA-elig 650.3 8.5 -7.8
Nick Adenhart 22 pre-arb 10.2 0.4 +0.0
Jose Arredondo 25 pre-arb 430.1 0.4 -0.2
Kelvim Escobar 33 FA-elig 10.1 10.0 -9.8
Dustin Moseley 27 arb 30.1 0.4 -0.3
Trevor Bell 22 80.1
Sean Rodríguez 24 pre-arb 120.0 0.4 -0.3
Terry Evans 27 pre-arb 11-0.0 0.4 -0.5
Bobby Mosebach 24 pre-arb 3-0.0 0.4 -0.5
Chris Pettit 24 10-0.0
Scot Shields 33 FA-elig 20-0.1 5.0 -5.2
Freddy Sandoval 26 pre-arb 5-0.1 0.4 -0.6
Daniel Davidson 28 4-0.1
Ryan Budde 29 pre-arb 3-0.1 0.4 -0.6
Fernando Rodriguez Jr. 25 1-0.1
Bobby Wilson 26 pre-arb 12-0.1 0.4 -0.6
Justin Speier 35 FA-elig 41-0.1 4.8 -5.0
Rich Thompson 24 pre-arb 13-0.2 0.4 -0.8
Rafael Rodriguez 24 pre-arb 18-0.2 0.4 -0.8
Anthony Ortega 23 pre-arb 3-0.2 0.4 -0.9
Sean O'Sullivan 21 12-0.2
Brandon Wood 24 pre-arb 18-0.3 0.4 -1.1
Robb Quinlan 32 FA-elig 54-0.7 1.1 -2.7
Reggie Willits 28 arb 49-0.7 0.5 -2.1
Gary Matthews Jr. 34 FA-elig 103-1.0 10.4 -12.7

Key additions (new vs 2008)

PlayerfWAR
Bobby Abreu2.7
Scott Kazmir1.3
Matt Palmer0.6
Brian Fuentes0.3
Kelvim Escobar0.1
Trevor Bell0.1

Key subtractions (gone from 2008)

Player2008 fWAR
Mark Teixeira3.5
Francisco Rodríguez1.5
Garret Anderson1.4
Jon Garland1.3
Casey Kotchman1.1
Darren O'Day0.3

Current & Historical Payroll

2009 payroll
$119.6M

What the front office was committed to as of 2009, 2009–2015. 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 (14)

PlayerAgeSvc2009201020112012201320142015
Torii Hunter3312.000$18.0M$18.5M$18.5M$18.0MFREE AGENT
Vladimir Guerrero3413.000$15.0MFREE AGENT
Gary Matthews Jr.3410.000$10.4MFREE AGENT
Kelvim Escobar3312.000$10.0MFREE AGENT
John Lackey307.000$10.0MFREE AGENT
Brian Fuentes338.000$8.5M$7.3MFREE AGENT
Chone Figgins317.000$5.8MFREE AGENT
Bobby Abreu3513.000$5.0M$9.0M$9.0M$8.3MFREE AGENT
Scot Shields338.000$5.0M$5.3MFREE AGENT
Justin Speier3511.000$4.8MFREE AGENT
Darren Oliver3816.000$3.7MFREE AGENT
Juan Rivera308.000$3.2M$4.2MFREE AGENT
Robb Quinlan326.000$1.1MFREE AGENT
Shane Loux297.000$405kFREE AGENT

Arbitration-eligible (13)

PlayerAgeSvc2009201020112012201320142015
Ervin Santana264.000$3.8MARB 3FREE AGENT
Mike Napoli273.000$2.0MARB 2ARB 3FREE AGENT
Maicer Izturis285.000$1.6MFREE AGENT
Scott Kazmir255.000$1.4MFREE AGENT
Kendrys Morales263.000$1.1MARB 2ARB 3FREE AGENT
Joe Saunders284.000$475kARB 3FREE AGENT
Howie Kendrick253.000$465kARB 2ARB 3FREE AGENT
Jered Weaver263.000$465kARB 2ARB 3FREE AGENT
Erick Aybar253.000$460kARB 2ARB 3FREE AGENT
Jeff Mathis264.000$450kARB 3FREE AGENT
Reggie Willits283.000$450kARB 2ARB 3FREE AGENT
Dustin Moseley273.000$435kARB 2ARB 3FREE AGENT
Jason Bulger304.000$403kARB 3FREE AGENT

Pre-arbitration (19)

PlayerAgeSvc2009201020112012201320142015
Jose Arredondo251.000$410kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Brandon Wood242.000$405kARB 1ARB 2ARB 3FREE AGENT
Sean Rodríguez241.000$405kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Kevin Jepsen241.000$401kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Bobby Wilson261.000$400kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Nick Adenhart221.000$400kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Rafael Rodriguez240.000$400kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Terry Evans272.000$400kARB 1ARB 2ARB 3FREE AGENT
Ryan Budde292.000$400kARB 1ARB 2ARB 3FREE AGENT
Bobby Mosebach240.000$400kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Rich Thompson242.000$400kARB 1ARB 2ARB 3FREE AGENT
Freddy Sandoval261.000$400kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Anthony Ortega230.000$400kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Daniel Davidson28
Fernando Rodriguez Jr.250.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Trevor Bell220.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Sean O'Sullivan210.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Matt Palmer301.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Chris Pettit240.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Angels 2009; 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.097, 0.23], [1986, 1.229, 13.6], [1987, 1.196, -3.59], [1988, 1.022, -5.93], [1989, 1.111, 6.82], [1990, 1.31, -0.96], [1991, 1.286, -10.76], [1992, 1.01, -15.75], [1993, 0.836, -11.48], [1994, 0.681, -8.81], [1995, 0.989, 9.18], [1996, 0.817, -6.77], [1997, 0.885, -0.3], [1998, 1.023, -10.63], [1999, 1.082, -12.12], [2000, 1.008, 1.8], [2001, 0.544, 4.82], [2002, 0.943, 11.85], [2003, 1.112, -3.97], [2004, 1.509, 3.79], [2005, 1.185, 4.69], [2006, 1.27, 1.18], [2007, 1.302, 4.26], [2008, 1.297, -2.68], [2009, 1.308, 5.3], [2010, 1.278, -9.39], [2011, 1.482, -0.62], [2012, 1.494, 4.77], [2013, 1.188, -4.62], [2014, 1.213, 8.8], [2015, 1.104, -6.16], [2016, 1.007, -8.93], [2017, 1.16, -8.3], [2018, 1.301, -4.89], [2019, 1.24, -12.89], [2021, 1.442, -10.62], [2022, 1.049, -7.46], [2023, 1.439, -11.79], [2024, 0.969, -21.21], [2025, 1.082, -21.86], [2026, 0.828, -6.47]], TEAMCOL = '#BA0021'; 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 = 2009; 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", "IF", "IF", "IF", "SP", "OF", "OF", "C", "IF", "SP", "RP", "RP", "SP", "SP", "C", "RP", "RP", "OF", "RP", "RP", "SP", "RP", "SP", "SP", "SP", "OF", "OF", "RP", "OF", "RP", "IF", "RP", "C", "RP", "C", "RP", "RP", "RP"], "league": [6.07, 4.44, 3.7, 3.11, 2.74, 2.31, 2.06, 1.82, 1.66, 1.45, 1.28, 1.12, 0.99, 0.85, 0.76, 0.66, 0.58, 0.51, 0.43, 0.36, 0.3, 0.25, 0.22, 0.17, 0.13, 0.09, 0.07, 0.04, 0.02, -0.0, -0.03, -0.05, -0.08, -0.1, -0.13, -0.15, -0.18, -0.2, -0.25, -0.31], "names": ["Chone Figgins", "Torii Hunter", "Jered Weaver", "Erick Aybar", "Kendrys Morales", "Maicer Izturis", "John Lackey", "Bobby Abreu", "Juan Rivera", "Mike Napoli", "Howie Kendrick", "Scott Kazmir", "Darren Oliver", "Kevin Jepsen", "Joe Saunders", "Ervin Santana", "Jeff Mathis", "Matt Palmer", "Jason Bulger", "Vladimir Guerrero", "Shane Loux", "Brian Fuentes", "Nick Adenhart", "Jose Arredondo", "Kelvim Escobar", "Dustin Moseley", "Trevor Bell", "Sean Rodr\u00edguez", "Terry Evans", "Bobby Mosebach", "Chris Pettit", "Scot Shields", "Freddy Sandoval", "Daniel Davidson", "Ryan Budde", "Fernando Rodriguez Jr.", "Bobby Wilson", "Justin Speier", "Rich Thompson", "Rafael Rodriguez"], "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": [6.56, 3.84, 3.83, 3.77, 3.7, 3.24, 3.22, 2.71, 2.58, 2.25, 2.14, 1.26, 1.22, 1.15, 0.97, 0.83, 0.64, 0.61, 0.58, 0.49, 0.47, 0.29, 0.17, 0.1, 0.07, 0.05, 0.05, 0.04, -0.03, -0.03, -0.04, -0.06, -0.07, -0.07, -0.07, -0.08, -0.09, -0.1, -0.18, -0.18]}; 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:'ANA',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);});