RRosternomics

Dodgers LAD

President of baseball ops: Andrew Friedman · GM: Farhan Zaidi

Overview

CONTEND ↘ cooling
expensive & winning · year 4 in this regime · payroll 1.82× league · TAB +1.3
+1.3TAB — talent above budget13th/30
+1.6WAB — realized wins (TAB + luck)13th/30
7.0depth — 2+ WAR regulars13th/30
-5.2dead WAR — sub-replacement drag17th/30
40%dead money %21st/30
0.562win %6th/30
42.7roster fWAR5th/30
-90surplus $M (value vs cost)29th/30
$228Mpayroll $M1st/30

2016: made playoffs · 91–71. Ranks are vs the 30 teams that season.

Roster

CFJoc Pederson 132GTrayce Thompson 32GEnrique Hernández 22G
LFHowie Kendrick 94GEnrique Hernández 41GTrayce Thompson 24G
RFJosh Reddick 110GYasiel Puig 90GTrayce Thompson 28G
SSCorey Seager 155GCharlie Culberson 11GChris Taylor 6G
2BChase Utley 134GHowie Kendrick 32GEnrique Hernández 11G
3BJustin Turner 144GHowie Kendrick 17GChris Taylor 10G
1BAdrián González 151GHowie Kendrick 11GRob Segedin 9G
STARTERSClayton Kershaw 6.3WARKenta Maeda 2.8WARJulio Urías 1.6WARAlex Wood 1.2WARRich Hill 1.1WAR
CYasmani Grandal 115GCarlos Ruiz 56GAustin Barnes 9G
DHCarl Crawford 4GAdrián González 2GJustin Turner 2G
BULLPEN Kenley Jansen 47svJoe Blanton 28hPedro Báez 23hAdam Liberatore 14hJesse Chavez 11hLouis Coleman 10hCasey Fien 6hJ.P. Howell 2h
CYasmani Grandal 115 · Carlos Ruiz 56 · Austin Barnes 9
1BAdrián González 151 · Howie Kendrick 11 · Rob Segedin 9
2BChase Utley 134 · Howie Kendrick 32 · Enrique Hernández 11
3BJustin Turner 144 · Howie Kendrick 17 · Chris Taylor 10
SSCorey Seager 155 · Charlie Culberson 11 · Chris Taylor 6
LFHowie Kendrick 94 · Enrique Hernández 41 · Trayce Thompson 24
CFJoc Pederson 132 · Trayce Thompson 32 · Enrique Hernández 22
RFJosh Reddick 110 · Yasiel Puig 90 · Trayce Thompson 28
DHCarl Crawford 4 · Adrián González 2 · Justin Turner 2
SPClayton Kershaw 6.3 · Kenta Maeda 2.8 · Julio Urías 1.6 · Alex Wood 1.2 · Rich Hill 1.1
BP Kenley Jansen 47 · Joe Blanton 28 · Pedro Báez 23 · Adam Liberatore 14 · Jesse Chavez 11 · Louis Coleman 10 · Casey Fien 6 · J.P. Howell 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.

2016 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Yasmani Grandal 27 arb 1266.3 2.8 +17.6
Clayton Kershaw 28 FA-elig 236.3 34.6 -14.2
Corey Seager 22 pre-arb 1575.2 0.5 +16.5
Justin Turner 31 FA-elig 1514.4 5.1 +9.1
Joc Pederson 24 pre-arb 1373.3 0.5 +10.2
Kenley Jansen 28 FA-elig 723.1 10.7 -0.7
Kenta Maeda 28 pre-arb 352.8 3.1 +5.9
Julio Urías 19 181.6 0.3 +4.8
Yasiel Puig 25 arb 1041.5 7.2 -2.4
Andrew Toles 24 481.3 0.2 +4.1
Alex Wood 25 arb 151.2 0.5 +3.4
Chase Utley 37 FA-elig 1381.2 7.0 -3.2
Howie Kendrick 32 FA-elig 1461.1 10.0 -6.5
Rich Hill↔ OAK 36 FA-elig 61.1 1.8 +1.6
Adrián González 34 FA-elig 1561.0 21.9 -18.7
Ross Stripling 26 220.9 0.3 +2.6
Scott Kazmir 32 FA-elig 270.8 12.7 -10.1
Joe Blanton 35 FA-elig 750.7 4.0 -1.6
Adam Liberatore 29 pre-arb 580.7 0.5 +1.8
Trayce Thompson 25 pre-arb 800.6 0.5 +1.5
Brandon McCarthy 32 FA-elig 100.6 12.5 -10.7
Luis Avilán 26 arb 270.5 1.4 +0.2
Grant Dayton 28 250.4 0.2 +1.2
Pedro Báez 28 pre-arb 730.3 0.5 +0.5
Chris Taylor↔ SEA 25 pre-arb 340.2 0.2 +0.6
J.P. Howell 33 FA-elig 640.2 6.2 -5.5
Josh Fields↔ HOU 30 arb 220.2 0.5 +0.2
Yimi García 25 pre-arb 90.2 0.5 +0.1
Charlie Culberson 27 arb 340.1 0.5 -0.1
Josh Ravin 28 pre-arb 100.1 0.5 -0.2
Carlos Frías 26 pre-arb 10.0 0.5 -0.4
Carlos Ruiz↔ PHI 37 FA-elig 140.0 1.9 -1.8
Nick Tepesch 27 10.0 0.0 +0.1
Jesse Chavez↔ TOR 32 FA-elig 23-0.0 1.5 -1.5
Hyun Jin Ryu 29 arb 1-0.0 7.8 -7.9
Enrique Hernández 24 pre-arb 109-0.1 0.5 -0.7
Bud Norris↔ ATL 31 FA-elig 13-0.1 0.9 -1.1
Chin-hui Tsao 35 2-0.1 0.4 -0.6
Andre Ethier 34 FA-elig 16-0.1 18.0 -18.2
Louis Coleman 30 arb 61-0.1 0.7 -1.0
Micah Johnson 25 pre-arb 7-0.1 0.0 -0.3
Rob Segedin 27 pre-arb 40-0.1 0.2 -0.6
Austin Barnes 26 pre-arb 21-0.1 0.5 -1.0
Zach Walters 26 3-0.2 0.0 -0.5
Scott Van Slyke 29 arb 52-0.2 1.2 -1.9
Brock Stewart 24 7-0.2 0.1 -0.9
Josh Reddick↔ OAK 29 FA-elig 47-0.3 2.7 -3.6
Will Venable 33 FA-elig 12-0.3 0.0 -1.0
Mike Bolsinger 28 6-0.3 0.2 -1.2
A.J. Ellis↔ PHI 35 FA-elig 53-0.4 3.7 -4.9
José De León 23 4-0.4 0.1 -1.3
Brett Anderson 28 FA-elig 4-0.4 15.8 -17.0
Chris Hatcher 31 FA-elig 37-0.4 1.1 -2.3
Casey Fien↔ MIN 32 FA-elig 25-0.6 1.5 -3.3
Carl Crawford 34 30-0.9 21.6 -24.5

Key additions (new vs 2015)

PlayerfWAR
Kenta Maeda2.8
Julio Urías1.6
Andrew Toles1.3
Rich Hill1.1
Ross Stripling0.9
Scott Kazmir0.8

Key subtractions (gone from 2015)

Player2015 fWAR
Zack Greinke6.0
Juan Nicasio0.7
Justin Ruggiano0.6
Juan Uribe0.6
Jimmy Rollins0.4
Scott Schebler0.4

Current & Historical Payroll

2016 payroll
$228.3M
2017 guaranteed
$0M

What the front office was committed to as of 2016, 2016–2022. Guaranteed $ are contract commitments signed by 2016; arbitration & free-agent years are projected from service time. Hover a figure for its luxury-tax (CBT) & actual-cash values.

current $ guaranteed $ arbitration (proj) pre-arb (proj) free agent club option player option mutual option vesting option

Guaranteed / signed (15)

PlayerAgeSvc2016201720182019202020212022
Clayton Kershaw287.105$34.6MFREE AGENT
Adrián González3410.108$21.9MFREE AGENT
Carl Crawford3413.072$21.6MFREE AGENT
Andre Ethier3411.153$18.0MFREE AGENT
Brett Anderson287.000$15.8MFREE AGENT
Scott Kazmir329.119$12.7MFREE AGENT
Brandon McCarthy329.122$12.5MFREE AGENT
Howie Kendrick329.091$10.0MFREE AGENT
Chase Utley3712.027$7.0MFREE AGENT
J.P. Howell339.007$6.2MFREE AGENT
Joe Blanton359.158$4.0MFREE AGENT
Carlos Ruiz379.069$1.9MFREE AGENT
Rich Hill366.127$1.8MFREE AGENT
Bud Norris316.068$929kFREE AGENT
Will Venable336.155$47kFREE AGENT

Arbitration-eligible (12)

PlayerAgeSvc2016201720182019202020212022
Kenley Jansen285.072$10.7MFREE AGENT
Hyun Jin Ryu293.000$7.8MARB 2ARB 3FREE AGENT
Justin Turner315.045$5.1MFREE AGENT
A.J. Ellis355.151$3.7MFREE AGENT
Yasmani Grandal273.115$2.8MARB 2ARB 3FREE AGENT
Josh Reddick295.050$2.7MFREE AGENT
Jesse Chavez325.108$1.5MFREE AGENT
Casey Fien323.143$1.5MARB 2ARB 3FREE AGENT
Luis Avilán263.077$1.4MARB 2ARB 3FREE AGENT
Scott Van Slyke294.000$1.2MARB 3FREE AGENT
Louis Coleman303.018$725kARB 2ARB 3FREE AGENT
Josh Fields303.000$535kARB 2ARB 3FREE AGENT

Pre-arbitration (28)

PlayerAgeSvc2016201720182019202020212022
Yasiel Puig252.119$7.2MARB 1ARB 2ARB 3FREE AGENT
Kenta Maeda280.000$3.1MPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Chris Hatcher312.146$1.1MARB 1ARB 2ARB 3FREE AGENT
Charlie Culberson271.074$535kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Alex Wood252.123$530kARB 1ARB 2ARB 3FREE AGENT
Joc Pederson241.028$520kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Pedro Báez281.059$520kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Enrique Hernández241.054$520kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Yimi García251.004$518kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Carlos Frías261.047$518kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Josh Ravin281.151$515kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Austin Barnes260.060$512kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Trayce Thompson250.063$512kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Corey Seager220.032$510kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Adam Liberatore292.086$477kARB 1ARB 2ARB 3FREE AGENT
Chin-hui Tsao35$380k
Ross Stripling260.000$316kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Julio Urías190.000$272kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Mike Bolsinger281.072$205kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Andrew Toles240.000$191kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Grant Dayton280.000$172kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Chris Taylor250.139$170kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Rob Segedin271.038$161kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Brock Stewart240.000$105kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
José De León230.000$80kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Micah Johnson251.046$44kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Zach Walters261.128$14kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Nick Tepesch272.136$3kARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Dodgers 2016; 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 = 2016; 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": ["C", "SP", "IF", "IF", "OF", "RP", "SP", "SP", "OF", "OF", "SP", "IF", "OF", "SP", "IF", "SP", "SP", "RP", "RP", "OF", "SP", "RP", "RP", "RP", "IF", "RP", "RP", "RP", "IF", "RP", "RP", "C", "SP", "RP", "SP", "OF", "SP", "RP", "OF", "RP"], "league": [5.58, 4.15, 3.63, 3.11, 2.71, 2.41, 2.16, 1.91, 1.69, 1.47, 1.27, 1.13, 1.02, 0.92, 0.82, 0.72, 0.63, 0.56, 0.48, 0.41, 0.37, 0.3, 0.27, 0.22, 0.18, 0.15, 0.12, 0.09, 0.06, 0.04, 0.01, -0.01, -0.03, -0.05, -0.07, -0.1, -0.12, -0.15, -0.18, -0.23], "names": ["Yasmani Grandal", "Clayton Kershaw", "Corey Seager", "Justin Turner", "Joc Pederson", "Kenley Jansen", "Kenta Maeda", "Julio Ur\u00edas", "Yasiel Puig", "Andrew Toles", "Alex Wood", "Chase Utley", "Howie Kendrick", "Rich Hill", "Adri\u00e1n Gonz\u00e1lez", "Ross Stripling", "Scott Kazmir", "Joe Blanton", "Adam Liberatore", "Trayce Thompson", "Brandon McCarthy", "Luis Avil\u00e1n", "Grant Dayton", "Pedro B\u00e1ez", "Chris Taylor", "J.P. Howell", "Josh Fields", "Yimi Garc\u00eda", "Charlie Culberson", "Josh Ravin", "Carlos Fr\u00edas", "Carlos Ruiz", "Nick Tepesch", "Jesse Chavez", "Hyun Jin Ryu", "Enrique Hern\u00e1ndez", "Bud Norris", "Chin-hui Tsao", "Andre Ethier", "Louis Coleman"], "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.28, 6.27, 5.24, 4.36, 3.31, 3.06, 2.79, 1.56, 1.5, 1.33, 1.2, 1.16, 1.08, 1.06, 0.98, 0.89, 0.8, 0.75, 0.71, 0.61, 0.56, 0.48, 0.41, 0.33, 0.24, 0.22, 0.21, 0.18, 0.14, 0.08, 0.05, 0.03, 0.03, -0.01, -0.03, -0.06, -0.06, -0.07, -0.07, -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);});