RRosternomics

Dodgers LAD

President of baseball ops: Andrew Friedman · GM: Brandon Gomes

Overview

CONTEND ↗ rising
expensive & winning · year 13 in this regime · payroll 2.19× league · TAB +12.3
+12.3TAB — talent above budget5th/30
+7.1WAB — realized wins (TAB + luck)8th/30
6.0depth — 2+ WAR regulars2nd/30
-1.6dead WAR — sub-replacement drag2nd/30
28%dead money %8th/30
0.635win %1st/30
55.9roster fWAR1st/30
+4surplus $M (value vs cost)27th/30
$350Mpayroll $M1st/30luxury tax $397M

Roster

CFAndy Pages 67GAlex Call 3G
LFTeoscar Hernández 50GAlex Call 22GRyan Ward 8G
RFKyle Tucker 64GAlex Call 6G
SSMookie Betts 33GHyeseong Kim 27GMiguel Rojas 20G
2BAlex Freeland 46GMiguel Rojas 18GHyeseong Kim 16G
3BMax Muncy 64GSantiago Espinal 17GMiguel Rojas 4G
1BFreddie Freeman 67GDalton Rushing 4GSantiago Espinal 3G
STARTERSShohei Ohtani 6.0WARYoshinobu Yamamoto 3.0WARJustin Wrobleski 2.8WARTyler Glasnow 1.1WAREmmet Sheehan 1.1WAR
CWill Smith 48GDalton Rushing 25G
DHShohei Ohtani 63GDalton Rushing 4GWill Smith 2G
BULLPEN Tanner Scott 7svAlex Vesia 10hWill Klein 8hBlake Treinen 6hKyle Hurt 5hEdgardo Henriquez 4hJack Dreyer 3hEdwin Díaz 0h
CWill Smith 48 · Dalton Rushing 25
1BFreddie Freeman 67 · Dalton Rushing 4 · Santiago Espinal 3
2BAlex Freeland 46 · Miguel Rojas 18 · Hyeseong Kim 16
3BMax Muncy 64 · Santiago Espinal 17 · Miguel Rojas 4
SSMookie Betts 33 · Hyeseong Kim 27 · Miguel Rojas 20
LFTeoscar Hernández 50 · Alex Call 22 · Ryan Ward 8
CFAndy Pages 67 · Alex Call 3
RFKyle Tucker 64 · Alex Call 6
DHShohei Ohtani 63 · Dalton Rushing 4 · Will Smith 2
SPShohei Ohtani 6.0 · Yoshinobu Yamamoto 3.0 · Justin Wrobleski 2.8 · Tyler Glasnow 1.1 · Emmet Sheehan 1.1
BP Tanner Scott 7 · Alex Vesia 10 · Will Klein 8 · Blake Treinen 6 · Kyle Hurt 5 · Edgardo Henriquez 4 · Jack Dreyer 3 · Edwin Díaz 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.

2026 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Shohei Ohtani 31 FA-elig 999.3 28.2 +30.2
Andy Pages 25 pre-arb 1035.1 0.8 +31.2
Max Muncy 35 FA-elig 955.0 10.0 +21.8
Freddie Freeman 36 FA-elig 1014.7 22.9 +6.6
Yoshinobu Yamamoto 27 pre-arb 184.7 20.3 +9.1
Justin Wrobleski 25 pre-arb 174.3 0.8 +26.2
Mookie Betts 33 FA-elig 672.2 25.2 -11.3
Tanner Scott 31 FA-elig 442.2 12.6 +1.1
Emmet Sheehan 26 pre-arb 181.7 0.8 +10.0
Tommy Edman 31 FA-elig 291.7 12.1 -1.6
Dalton Rushing 25 pre-arb 681.7 0.8 +9.7
Tyler Glasnow 32 FA-elig 71.6 32.5 -22.2
Will Smith 31 FA-elig 521.6 12.9 -3.1
Alex Vesia 30 arb 451.4 3.5 +5.6
Will Klein 26 pre-arb 341.3 0.8 +7.7
Edgardo Henriquez 24 pre-arb 391.2 0.8 +6.9
Kyle Tucker 29 FA-elig 971.2 60.0 -52.6
Jack Dreyer 27 pre-arb 410.9 0.8 +4.8
Roki Sasaki 24 pre-arb 180.8 0.8 +4.5
Miguel Rojas 37 FA-elig 690.8 5.5 -0.6
Teoscar Hernández 33 FA-elig 690.7 14.7 -10.0
Kyle Hurt 28 pre-arb 330.6 0.7 +3.3
Alex Call 31 pre-arb 590.6 1.6 +2.2
Alex Freeland 24 pre-arb 710.6 0.2 +3.6
Hyeseong Kim 27 pre-arb 430.5 4.1 -0.7
Enrique Hernández 34 FA-elig 20.5 4.5 -1.5
Blake Treinen 38 FA-elig 290.5 13.5 -10.6
Ryan Ward 28 200.4 0.0 +2.5
Blake Snell 33 FA-elig 10.2 27.9 -26.7
Paul Gervase 26 pre-arb 40.2 0.6 +0.4
Eric Lauer↔ TOR 31 arb 80.1 1.6 -0.7
Brock Stewart 34 arb 120.1 1.3 -0.7
Santiago Espinal 31 arb 360.1 0.8 -0.3
Jake Eder 27 pre-arb 40.0 0.1 +0.2
Chayce McDermott 27 pre-arb 2-0.0 0.0 -0.3
Edwin Díaz 32 FA-elig 7-0.1 17.0 -17.4
Wyatt Mills 31 pre-arb 5-0.1 0.0 -0.7
Ben Casparius 27 pre-arb 5-0.2 0.8 -1.8
Evan Phillips 31 arb 6-0.2 6.5 -7.8
Eliezer Alfonzo 26 8-0.3
Charlie Barnes↔ CHC 30 pre-arb 3-0.3 0.0 -1.9
Jonathan Hernández 29 arb 12-0.3 0.6 -2.6
Landon Knack 28 pre-arb 2-0.4 0.8 -3.2
Chuckie Robinson 31 8-0.7 0.5 -4.7

Key additions (new vs 2025)

PlayerfWAR
Kyle Tucker0.8
Kyle Hurt0.4
Ryan Ward0.3
Eric Lauer0.1
Santiago Espinal0.1
Jake Eder0.0

Key subtractions (gone from 2025)

Player2025 fWAR
Clayton Kershaw2.5
Dustin May0.9
Luis García0.2
Lou Trivino III0.2
Ben Rortvedt0.2
Anthony Banda0.1

Current & Historical Payroll

2026 payroll
$349.9M
luxury tax $397M
2027 guaranteed
$325.2M

What the front office was committed to as of 2026, 2026–2032. Guaranteed $ are contract commitments signed by 2026; 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

Summary — luxury-tax (CBT) basis

2026202720282029203020312032
Tax threshold$244M$247M$250M$253M$256M$259M$262M
Guaranteed contracts$362M$333M$304M$245M$165M$116M
Proj. arbitration$11M
Proj. pre-arbitration$24M$10M$10M$18M
Player benefits$18M$18M$18M$18M$18M$18M
Total CBT payroll$415M$361M$332M$281M$183M$134M
Tax space−$-171M−$-114M−$-82M−$-28M+$73M+$125M
Est. tax bill$164M$101M$65M$15M

Luxury-tax (CBT) basis: contract AAV + ~$18M benefits/bonus-pool — distinct from the base-salary payroll above. Threshold is the CBA line. Tier-3 payor rates on the overage. Forward years show only guaranteed deals (arb/pre-arb not yet set). Figures are estimates.

Guaranteed / signed (17)

PlayerAgeSvc2026202720282029203020312032
Kyle Tucker296.079$60.0M$55.0M$60.0MOPT-OUT$60.0MOPT-OUTFREE AGENT
Tyler Glasnow328.158$32.5M$32.5M$30.0MCLUBFREE AGENT
Shohei Ohtani318.000$28.2M$28.2M$28.2M$28.2M$28.2M$28.2M$28.2M
Blake Snell339.072$27.9M$27.3M$36.4M$26.5M$10.0MCLUBFREE AGENT
Mookie Betts3311.070$25.2M$29.7M$29.7M$29.7M$27.2M$27.2M$27.2M
Freddie Freeman3615.033$22.9M$23.1MFREE AGENT
Yoshinobu Yamamoto272.000$20.3M$34.3M$34.3M$34.3M$33.2MOPT-OUT$33.2M$32.2MOPT-OUT
Edwin Díaz329.121$17.0M$26.0M$26.0M$6.5MCLUBFREE AGENT
Teoscar Hernández338.097$14.7M$16.9M$15.0MCLUBFREE AGENT
Blake Treinen3811.065$13.5MFREE AGENT
Will Smith316.090$12.9M$12.9M$9.4M$9.4M$9.4M$9.4M$9.4M
Tanner Scott317.059$12.6M$16.4M$16.2M$5.0MCLUBFREE AGENT
Tommy Edman316.114$12.1M$11.8M$11.5M$11.2M$13.0MCLUBFREE AGENT
Max Muncy350.144$10.0M$7.0M$10.0MCLUBARB 1ARB 2ARB 3FREE AGENT
Miguel Rojas3711.043$5.5MFREE AGENT
Enrique Hernández3411.054$4.5MFREE AGENT
Hyeseong Kim270.149$4.1M$4.1M$5.0MCLUB$5.0MCLUBARB 2ARB 3FREE AGENT

Arbitration-eligible (6)

PlayerAgeSvc2026202720282029203020312032
Evan Phillips315.136$6.5MFREE AGENT
Alex Vesia305.078$3.5MCLUBFREE AGENT
Eric Lauer315.091$1.6MFREE AGENT
Brock Stewart344.093$1.3MARB 3FREE AGENT
Santiago Espinal315.149$829kFREE AGENT
Jonathan Hernández294.096$603kARB 3FREE AGENT

Pre-arbitration (20)

PlayerAgeSvc2026202720282029203020312032
Alex Call312.161$1.6MARB 1ARB 2ARB 3FREE AGENT
Jack Dreyer271.000$810kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Emmet Sheehan262.080$810kARB 1ARB 2ARB 3FREE AGENT
Roki Sasaki241.000$800kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Andy Pages251.155$800kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Ben Casparius271.016$800kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Dalton Rushing250.138$790kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Justin Wrobleski250.150$790kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Edgardo Henriquez240.164$790kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Will Klein260.081$790kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Landon Knack280.119$790kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Kyle Hurt281.099$732kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Paul Gervase260.021$563kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Chuckie Robinson311.129$450kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Alex Freeland240.038$201kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jake Eder270.042$67kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Wyatt Mills311.134$38kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Ryan Ward28$8k
Charlie Barnes300.037$6kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Chayce McDermott270.056$4kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Dodgers 2026; 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 = 2026; 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", "IF", "IF", "SP", "SP", "IF", "RP", "SP", "IF", "C", "SP", "C", "RP", "RP", "RP", "OF", "RP", "SP", "IF", "OF", "RP", "OF", "IF", "IF", "IF", "RP", "OF", "SP", "RP", "SP", "RP", "IF", "RP", "RP", "RP", "RP", "RP", "RP", "C"], "league": [3.73, 2.8, 2.36, 2.09, 1.83, 1.64, 1.41, 1.24, 1.09, 0.96, 0.87, 0.78, 0.68, 0.62, 0.56, 0.49, 0.42, 0.36, 0.3, 0.22, 0.19, 0.16, 0.12, 0.1, 0.08, 0.06, 0.04, 0.03, 0.01, -0.01, -0.03, -0.04, -0.07, -0.09, -0.1, -0.12, -0.15, -0.18, -0.22, -0.25], "names": ["Shohei Ohtani", "Andy Pages", "Max Muncy", "Freddie Freeman", "Yoshinobu Yamamoto", "Justin Wrobleski", "Mookie Betts", "Tanner Scott", "Emmet Sheehan", "Tommy Edman", "Dalton Rushing", "Tyler Glasnow", "Will Smith", "Alex Vesia", "Will Klein", "Edgardo Henriquez", "Kyle Tucker", "Jack Dreyer", "Roki Sasaki", "Miguel Rojas", "Teoscar Hern\u00e1ndez", "Kyle Hurt", "Alex Call", "Alex Freeland", "Hyeseong Kim", "Enrique Hern\u00e1ndez", "Blake Treinen", "Ryan Ward", "Blake Snell", "Paul Gervase", "Eric Lauer", "Brock Stewart", "Santiago Espinal", "Jake Eder", "Chayce McDermott", "Edwin D\u00edaz", "Wyatt Mills", "Ben Casparius", "Evan Phillips", "Eliezer Alfonzo"], "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.95, 3.27, 3.24, 3.01, 3.0, 2.75, 1.41, 1.4, 1.1, 1.07, 1.07, 1.05, 1.0, 0.93, 0.86, 0.78, 0.75, 0.57, 0.54, 0.5, 0.48, 0.41, 0.39, 0.39, 0.35, 0.31, 0.3, 0.25, 0.12, 0.1, 0.09, 0.06, 0.05, 0.03, -0.03, -0.04, -0.07, -0.1, -0.14, -0.19]}; 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);});