RRosternomics

Dodgers LAD

GM: Ned Colletti

Overview

CONTEND ↘ cooling
expensive & winning · year 1 in this regime · payroll 1.32× league · TAB +2.9
+2.9TAB — talent above budget11th/30
+2.6WAB — realized wins (TAB + luck)11th/30
8.0depth — 2+ WAR regulars10th/30
-3.2dead WAR — sub-replacement drag4th/30
30%dead money %15th/30
0.543win %8th/30
40.5roster fWAR6th/30
-38surplus $M (value vs cost)25th/30
$100Mpayroll $M5th/30

2006: made playoffs · 88–74. Ranks are vs the 30 teams that season.

Roster

CFKenny Lofton 120GJason Repko 40GMatt Kemp 29G
LFAndre Ethier 109GJosé Cruz Jr. 40GMarlon Anderson 15G
RFJ.D. Drew 135GJosé Cruz Jr. 24GJason Repko 14G
SSRafael Furcal 156GJulio Lugo 81GRamon Martinez 12G
2BJeff Kent 108GRamon Martinez 39GMarlon Anderson 33G
3BWilson Betemit 79GBill Mueller 30GCésar Izturis 28G
1BNomar Garciaparra 118GJames Loney 39GOlmedo Saenz 30G
STARTERSDerek Lowe 4.3WARBrad Penny 3.7WARGreg Maddux 1.5WARHung-Chih Kuo 1.4WARAaron Sele 1.3WAR
CRussell Martin 117GToby Hall 82GEinar Díaz 1G
DHJ.D. Drew 4GOlmedo Saenz 3GKenny Lofton 2G
BULLPEN Takashi Saito 24svElmer Dessens 18hJonathan Broxton 12hJoe Beimel 10hTim Hamulack 4hGiovanni Carrara 2hFranquelis Osoria 0hLance Carter 0h
CRussell Martin 117 · Toby Hall 82 · Einar Díaz 1
1BNomar Garciaparra 118 · James Loney 39 · Olmedo Saenz 30
2BJeff Kent 108 · Ramon Martinez 39 · Marlon Anderson 33
3BWilson Betemit 79 · Bill Mueller 30 · César Izturis 28
SSRafael Furcal 156 · Julio Lugo 81 · Ramon Martinez 12
LFAndre Ethier 109 · José Cruz Jr. 40 · Marlon Anderson 15
CFKenny Lofton 120 · Jason Repko 40 · Matt Kemp 29
RFJ.D. Drew 135 · José Cruz Jr. 24 · Jason Repko 14
DHJ.D. Drew 4 · Olmedo Saenz 3 · Kenny Lofton 2
SPDerek Lowe 4.3 · Brad Penny 3.7 · Greg Maddux 1.5 · Hung-Chih Kuo 1.4 · Aaron Sele 1.3
BP Takashi Saito 24 · Elmer Dessens 18 · Jonathan Broxton 12 · Joe Beimel 10 · Tim Hamulack 4 · Giovanni Carrara 2 · Franquelis Osoria 0 · Lance Carter 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.

2006 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Derek Lowe 33 FA-elig 354.3 9.5 -1.0
J.D. Drew 30 FA-elig 1464.2 11.4 -3.0
Rafael Furcal 28 FA-elig 1593.8 8.7 -1.1
Brad Penny 28 FA-elig 343.7 5.2 +2.1
Takashi Saito 36 723.1
Russell Martin 23 1212.4
Andre Ethier 24 1262.2
Nomar Garciaparra 32 FA-elig 1222.0 6.0 -2.0
Jeff Kent 38 FA-elig 1151.7 9.4 -6.1
Greg Maddux↔ CHC 40 FA-elig 121.5 3.2 -0.1
Jonathan Broxton 22 681.4 0.3 +2.5
Hung-Chih Kuo 24 pre-arb 281.4 0.3 +2.4
Aaron Sele 36 281.3
Olmedo Saenz 35 FA-elig 1031.2 1.0 +1.3
Kenny Lofton 39 FA-elig 1291.0 3.8 -1.7
Brett Tomko 33 FA-elig 451.0 3.6 -1.7
Marlon Anderson↔ WSN 32 FA-elig 250.9 0.2 +1.7
Mark Hendrickson↔ TBR 32 arb 180.9 1.1 +0.7
James Loney 22 480.8 0.3 +1.2
Danys Baez↔ ATL 28 arb 460.7 3.2 -1.9
Chad Billingsley 21 180.6 0.3 +0.9
José Cruz Jr. 32 FA-elig 860.6 2.9 -1.8
Cody Ross↔ CIN 25 arb 80.5 0.0 +1.0
Willy Aybar↔ ATL 23 430.5
Cesar Izturis↔ CHC 26 arb 320.4 1.9 -1.0
Jason Repko 25 pre-arb 690.4 0.3 +0.5
Odalis Perez↔ KCR 28 FA-elig 210.3 5.6 -4.9
Toby Hall↔ TBR 30 FA-elig 210.2 0.6 -0.2
Dioner Navarro↔ TBR 22 pre-arb 250.2 0.1 +0.3
Joe Beimel 29 620.1 0.3 -0.0
Yhency Brazoban 26 pre-arb 50.1 0.3 -0.1
Eric Stults 26 60.1
Einar Díaz 33 30.1
Giovanni Carrara 38 250.1
Eric Gagne 30 FA-elig 20.0 10.0 -10.0
Joel Guzman 21 8-0.0
Ramon Martinez 33 FA-elig 82-0.0 0.6 -0.7
Jae Weong Seo↔ TBR 29 arb 19-0.0 0.2 -0.3
Ricky Ledee↔ NYM 32 FA-elig 43-0.0 0.9 -1.0
Lance Carter 31 FA-elig 10-0.1 0.6 -0.7
Delwyn Young 24 8-0.1
Wilson Betemit↔ ATL 24 arb 55-0.2 0.1 -0.4
Elmer Dessens↔ KCR 35 FA-elig 19-0.2 0.5 -0.8
Bill Mueller 35 FA-elig 32-0.2 4.6 -4.9
Oscar Robles 30 29-0.2
Franquelis Osoria 24 pre-arb 12-0.3 0.3 -1.0
Sandy Alomar Jr.↔ CHW 40 FA-elig 27-0.3 0.4 -1.0
Matt Kemp 21 52-0.4
Tim Hamulack 29 pre-arb 33-0.4 0.3 -1.2
Julio Lugo↔ TBR 30 FA-elig 49-0.8 2.0 -3.5

Key additions (new vs 2005)

PlayerfWAR
Rafael Furcal3.8
Takashi Saito3.1
Russell Martin2.4
Andre Ethier2.2
Nomar Garciaparra2.0
Greg Maddux1.5

Key subtractions (gone from 2005)

Player2005 fWAR
Jeff Weaver2.8
Milton Bradley1.6
Jayson Werth1.2
Hee-Seop Choi1.1
Antonio Perez0.9
Duaner Sanchez0.5

Current & Historical Payroll

2006 payroll
$100.3M

What the front office was committed to as of 2006, 2006–2012. 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 (25)

PlayerAgeSvc2006200720082009201020112012
J.D. Drew308.000$11.4MFREE AGENT
Eric Gagne307.000$10.0MFREE AGENT
Derek Lowe339.000$9.5M$9.5M$10.0MFREE AGENT
Jeff Kent3814.000$9.4M$9.8M$9.0MFREE AGENT
Rafael Furcal286.000$8.7M$13.7M$15.7M$7.5M$9.5M$7.5MFREE AGENT
Nomar Garciaparra3210.000$6.0M$8.5M$9.5MFREE AGENT
Odalis Perez288.000$5.6MFREE AGENT
Brad Penny286.000$5.2M$7.8M$9.2MFREE AGENT
Bill Mueller3510.000$4.6MFREE AGENT
Kenny Lofton3915.000$3.8MFREE AGENT
Brett Tomko339.000$3.6M$3.4MFREE AGENT
Greg Maddux4020.000$3.2MFREE AGENT$7.9M
José Cruz Jr.329.000$2.9MFREE AGENT
Julio Lugo306.000$2.0MFREE AGENT
Olmedo Saenz3512.000$1.0M$1.0MFREE AGENT
Ricky Ledee328.000$921kFREE AGENT
Ramon Martinez338.000$600k$800kFREE AGENT
Toby Hall306.000$556kFREE AGENT
Lance Carter317.000$550kFREE AGENT
Elmer Dessens3510.000$521kFREE AGENT
Sandy Alomar Jr.4018.000$382kFREE AGENT
Marlon Anderson328.000$173k$603kFREE AGENT
Giovanni Carrara3811.000FREE AGENT
Einar Díaz3310.000FREE AGENT
Aaron Sele3613.000FREE AGENT

Arbitration-eligible (7)

PlayerAgeSvc2006200720082009201020112012
Danys Baez285.000$3.2MFREE AGENT
Cesar Izturis265.000$1.9MFREE AGENT
Mark Hendrickson324.000$1.1MARB 3FREE AGENT
Joe Beimel295.000$327kFREE AGENT
Jae Weong Seo294.000$185kARB 3FREE AGENT
Wilson Betemit245.000$133kFREE AGENT
Cody Ross253.000$26kARB 2ARB 3FREE AGENT

Pre-arbitration (18)

PlayerAgeSvc2006200720082009201020112012
Yhency Brazoban262.000$345kARB 1ARB 2ARB 3FREE AGENT
Jason Repko251.000$338kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Franquelis Osoria241.000$328kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tim Hamulack291.000$328kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jonathan Broxton221.000$328kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Hung-Chih Kuo241.000$328kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Chad Billingsley210.000$327kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
James Loney220.000$327kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Dioner Navarro222.000$102kARB 1ARB 2ARB 3FREE AGENT
Oscar Robles30
Willy Aybar231.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Russell Martin230.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Joel Guzman21
Delwyn Young240.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Andre Ethier240.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Eric Stults260.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Matt Kemp210.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Takashi Saito360.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Dodgers 2006; 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.065, 14.01], [1986, 1.294, -8.47], [1987, 1.188, -10.83], [1988, 1.354, 2.04], [1989, 1.527, -5.24], [1990, 1.2, 0.19], [1991, 1.494, 0.26], [1992, 1.398, -16.39], [1993, 1.226, -7.56], [1994, 1.087, -3.62], [1995, 1.059, 4.0], [1996, 1.1, -2.31], [1997, 1.186, -1.79], [1998, 1.402, -8.63], [1999, 1.56, -4.82], [2000, 1.621, 0.18], [2001, 1.687, -5.86], [2002, 1.3, 3.31], [2003, 1.648, -2.98], [2004, 1.235, 7.4], [2005, 1.001, -9.38], [2006, 1.317, 2.93], [2007, 1.41, -1.56], [2008, 1.275, -0.9], [2009, 1.225, 9.9], [2010, 1.077, -4.75], [2011, 1.071, 4.66], [2012, 1.282, -2.48], [2013, 2.205, 2.84], [2014, 2.016, 9.2], [2015, 1.825, 7.32], [2016, 1.818, 1.34], [2017, 1.441, 12.89], [2018, 1.295, 16.35], [2019, 1.34, 22.24], [2021, 2.348, 12.31], [2022, 1.867, 20.65], [2023, 1.306, 11.59], [2024, 1.636, 8.14], [2025, 2.134, 6.43], [2026, 2.193, 12.32]], TEAMCOL = '#005A9C'; 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 = 2006; 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": ["SP", "OF", "IF", "SP", "RP", "C", "OF", "IF", "IF", "SP", "RP", "RP", "SP", "IF", "OF", "RP", "IF", "SP", "IF", "RP", "SP", "OF", "OF", "IF", "IF", "OF", "SP", "C", "C", "RP", "RP", "RP", "C", "RP", "RP", "IF", "IF", "SP", "OF", "RP"], "league": [5.57, 4.63, 3.8, 3.2, 2.81, 2.44, 2.09, 1.9, 1.64, 1.47, 1.27, 1.09, 0.99, 0.84, 0.72, 0.63, 0.53, 0.47, 0.4, 0.35, 0.29, 0.23, 0.18, 0.15, 0.13, 0.1, 0.06, 0.03, 0.01, -0.02, -0.05, -0.07, -0.09, -0.12, -0.15, -0.17, -0.23, -0.3, -0.35, -0.36], "names": ["Derek Lowe", "J.D. Drew", "Rafael Furcal", "Brad Penny", "Takashi Saito", "Russell Martin", "Andre Ethier", "Nomar Garciaparra", "Jeff Kent", "Greg Maddux", "Jonathan Broxton", "Hung-Chih Kuo", "Aaron Sele", "Olmedo Saenz", "Kenny Lofton", "Brett Tomko", "Marlon Anderson", "Mark Hendrickson", "James Loney", "Danys Baez", "Chad Billingsley", "Jos\u00e9 Cruz Jr.", "Cody Ross", "Willy Aybar", "Cesar Izturis", "Jason Repko", "Odalis Perez", "Toby Hall", "Dioner Navarro", "Joe Beimel", "Yhency Brazoban", "Eric Stults", "Einar D\u00edaz", "Giovanni Carrara", "Eric Gagne", "Joel Guzman", "Ramon Martinez", "Jae Weong Seo", "Ricky Ledee", "Lance Carter"], "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": [4.26, 4.19, 3.81, 3.69, 3.14, 2.4, 2.16, 2.03, 1.66, 1.53, 1.41, 1.38, 1.33, 1.18, 1.05, 0.96, 0.94, 0.9, 0.76, 0.66, 0.63, 0.55, 0.51, 0.46, 0.44, 0.4, 0.34, 0.2, 0.19, 0.15, 0.13, 0.09, 0.07, 0.07, 0.02, -0.01, -0.03, -0.04, -0.05, -0.07]}; 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:'LAD',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);});