RRosternomics

Dodgers LAD

President of baseball ops: Andrew Friedman

Overview

CONTEND ↗ rising
expensive & winning · year 7 in this regime · payroll 1.34× league · TAB +22.2
+22.2TAB — talent above budget4th/30
+20.4WAB — realized wins (TAB + luck)4th/30
11.0depth — 2+ WAR regulars2nd/30
-1.8dead WAR — sub-replacement drag3rd/30
7%dead money %2nd/30
0.654win %2nd/30
60.0roster fWAR2nd/30
+22surplus $M (value vs cost)7th/30
$174Mpayroll $M7th/30

2019: made playoffs · 106–56. Ranks are vs the 30 teams that season.

Roster

CFAJ Pollock 62GAlex Verdugo 61GCody Bellinger 25G
LFJoc Pederson 84GChris Taylor 56GMatt Beaty 34G
RFCody Bellinger 115GJoc Pederson 39GAlex Verdugo 25G
SSCorey Seager 132GChris Taylor 39GEnrique Hernández 11G
2BEnrique Hernández 85GMax Muncy 70GGavin Lux 22G
3BJustin Turner 124GMax Muncy 35GJedd Gyorko 21G
1BMax Muncy 65GTyler White 56GDavid Freese 50G
STARTERSHyun Jin Ryu 5.3WARWalker Buehler 5.1WARClayton Kershaw 3.3WARKenta Maeda 3.1WARRoss Stripling 1.9WAR
CAustin Barnes 64GRussell Martin 60GWill Smith 46G
DHDavid Freese 3GJustin Turner 3GJoc Pederson 2G
BULLPEN Kenley Jansen 33svPedro Báez 25hAdam Kolarek 19hJoe Kelly 8hDylan Floro 6hCaleb Ferguson 5hYimi García 4hCasey Sadler 2h
CAustin Barnes 64 · Russell Martin 60 · Will Smith 46
1BMax Muncy 65 · Tyler White 56 · David Freese 50
2BEnrique Hernández 85 · Max Muncy 70 · Gavin Lux 22
3BJustin Turner 124 · Max Muncy 35 · Jedd Gyorko 21
SSCorey Seager 132 · Chris Taylor 39 · Enrique Hernández 11
LFJoc Pederson 84 · Chris Taylor 56 · Matt Beaty 34
CFAJ Pollock 62 · Alex Verdugo 61 · Cody Bellinger 25
RFCody Bellinger 115 · Joc Pederson 39 · Alex Verdugo 25
DHDavid Freese 3 · Justin Turner 3 · Joc Pederson 2
SPHyun Jin Ryu 5.3 · Walker Buehler 5.1 · Clayton Kershaw 3.3 · Kenta Maeda 3.1 · Ross Stripling 1.9
BP Kenley Jansen 33 · Pedro Báez 25 · Adam Kolarek 19 · Joe Kelly 8 · Dylan Floro 6 · Caleb Ferguson 5 · Yimi García 4 · Casey Sadler 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.

2019 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Cody Bellinger 23 pre-arb 1567.8 0.6 +24.8
Hyun Jin Ryu 32 FA-elig 305.3 17.9 -0.6
Walker Buehler 24 pre-arb 305.1 0.6 +16.0
Max Muncy 28 arb 1414.4 0.6 +13.8
Justin Turner 34 FA-elig 1353.9 19.0 -6.2
Corey Seager 25 arb 1343.6 4.0 +7.7
Clayton Kershaw 31 FA-elig 293.3 31.0 -20.3
Kenta Maeda 31 arb 413.1 3.1 +7.1
Joc Pederson 27 arb 1492.8 5.0 +4.3
Alex Verdugo 23 pre-arb 1062.2 0.6 +6.5
Chris Taylor 28 arb 1242.1 3.5 +3.4
Ross Stripling 29 arb 321.9 0.6 +5.8
Will Smith 24 541.9 0.2 +5.9
Julio Urías 22 arb 371.7 0.6 +4.9
David Freese 36 FA-elig 791.7 4.5 +1.0
Pedro Báez 31 arb 711.4 2.1 +2.5
Kenley Jansen 31 FA-elig 621.2 19.3 -15.3
Russell Martin 36 FA-elig 831.1 20.0 -16.5
Rich Hill 39 FA-elig 131.0 18.7 -15.3
Dustin May 21 140.9 0.2 +2.8
Enrique Hernández 27 arb 1300.8 3.7 -1.1
Tony Gonsolin 25 110.8 0.1 +2.5
Dylan Floro 28 arb 500.6 0.6 +1.5
Austin Barnes 29 arb 750.6 0.6 +1.4
AJ Pollock 31 FA-elig 860.6 4.0 -2.1
Matt Beaty 26 990.4 0.4 +1.0
Joe Kelly 31 FA-elig 550.4 3.3 -2.0
Edwin Rios 25 280.4 0.1 +1.1
Gavin Lux 21 230.3 0.1 +0.9
Kyle Garlick 27 300.2 0.2 +0.6
Kristopher Negrón↔ SEA 33 300.2
Adam Kolarek↔ TBR 30 pre-arb 270.2 0.2 +0.4
JT Chargois 28 pre-arb 210.1 0.3 +0.0
Casey Sadler↔ TBR 28 pre-arb 240.0 0.1 -0.0
Travis d'Arnaud↔ NYM 30 FA-elig 1-0.0 0.0 -0.1
Brock Stewart↔ TOR 27 arb 3-0.1 0.1 -0.3
Jaime Schultz 28 pre-arb 4-0.1 0.1 -0.3
Caleb Ferguson 22 pre-arb 46-0.1 0.6 -0.8
Dennis Santana 23 pre-arb 3-0.1 0.1 -0.3
Josh Sborz 25 7-0.1 0.1 -0.4
Zac Rosscup↔ SEA 31 FA-elig 7-0.1 0.2 -0.5
Scott Alexander 29 arb 28-0.1 0.6 -1.0
Rocky Gale 31 5-0.2 0.1 -0.8
Yimi García 28 arb 64-0.3 0.7 -1.6
Jedd Gyorko↔ STL 30 FA-elig 24-0.3 5.0 -6.0
Tyler White↔ HOU 28 arb 12-0.5 0.1 -1.7

Key additions (new vs 2018)

PlayerfWAR
Will Smith1.9
Russell Martin1.1
Dustin May0.9
Tony Gonsolin0.8
AJ Pollock0.6
Matt Beaty0.4

Key subtractions (gone from 2018)

Player2018 fWAR
Yasmani Grandal5.3
Manny Machado2.7
Yasiel Puig2.3
Alex Wood2.1
Ryan Madson0.2
Kyle Farmer0.1

Current & Historical Payroll

2019 payroll
$173.5M
2020 guaranteed
$0M

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

PlayerAgeSvc2019202020212022202320242025
Clayton Kershaw3110.105$31.0MFREE AGENT
Russell Martin3612.150$20.0MFREE AGENT
Kenley Jansen318.072$19.3MFREE AGENT
Justin Turner348.045$19.0MFREE AGENT
Rich Hill399.127$18.7MFREE AGENT
Hyun Jin Ryu326.000$17.9MFREE AGENT
David Freese369.028$4.5MFREE AGENT
AJ Pollock316.052$4.0MFREE AGENT
Joe Kelly316.029$3.3MFREE AGENT
Kristopher Negrón337.000FREE AGENT

Arbitration-eligible (11)

PlayerAgeSvc2019202020212022202320242025
Jedd Gyorko305.164$5.0MFREE AGENT
Joc Pederson274.028$5.0MARB 3FREE AGENT
Corey Seager253.032$4.0MARB 2ARB 3FREE AGENT
Enrique Hernández274.054$3.7MARB 3FREE AGENT
Chris Taylor283.037$3.5MARB 2ARB 3FREE AGENT
Kenta Maeda313.000$3.1MARB 2ARB 3FREE AGENT
Pedro Báez314.059$2.1MARB 3FREE AGENT
Yimi García283.149$710kARB 2ARB 3FREE AGENT
Will Smith245.155$242kFREE AGENT
Zac Rosscup313.164$152kARB 2ARB 3FREE AGENT
Travis d'Arnaud305.044$34kFREE AGENT

Pre-arbitration (25)

PlayerAgeSvc2019202020212022202320242025
Cody Bellinger231.160$605kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Ross Stripling292.115$595kARB 1ARB 2ARB 3FREE AGENT
Scott Alexander292.097$575kARB 1ARB 2ARB 3FREE AGENT
Max Muncy282.027$575kARB 1ARB 2ARB 3FREE AGENT
Austin Barnes292.124$575kARB 1ARB 2ARB 3FREE AGENT
Walker Buehler240.168$570kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Julio Urías221.122$565kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Dylan Floro281.049$565kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Alex Verdugo230.078$560kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Caleb Ferguson220.113$560kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Matt Beaty260.000$406kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
JT Chargois281.062$273kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Adam Kolarek300.122$187kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Dustin May210.000$176kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Kyle Garlick270.000$162kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Casey Sadler280.111$150kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jaime Schultz280.069$148kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tony Gonsolin250.000$146kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Josh Sborz250.000$143kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Brock Stewart271.059$130kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Edwin Rios250.000$128kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Dennis Santana230.125$108kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tyler White281.101$84kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Gavin Lux210.000$84kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Rocky Gale310.077$69kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Dodgers 2019; 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 = 2019; 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": ["OF", "SP", "SP", "IF", "IF", "IF", "SP", "SP", "OF", "OF", "OF", "RP", "C", "RP", "IF", "RP", "RP", "C", "SP", "RP", "IF", "SP", "RP", "C", "OF", "IF", "RP", "IF", "IF", "OF", "OF", "RP", "RP", "RP", "C", "RP", "RP", "RP", "RP", "RP"], "league": [5.36, 4.35, 3.72, 3.13, 2.78, 2.49, 2.13, 1.9, 1.67, 1.5, 1.35, 1.18, 1.06, 0.95, 0.83, 0.72, 0.66, 0.59, 0.53, 0.46, 0.37, 0.32, 0.26, 0.22, 0.18, 0.15, 0.12, 0.1, 0.08, 0.03, 0.01, -0.01, -0.03, -0.06, -0.08, -0.1, -0.12, -0.14, -0.16, -0.19], "names": ["Cody Bellinger", "Hyun Jin Ryu", "Walker Buehler", "Max Muncy", "Justin Turner", "Corey Seager", "Clayton Kershaw", "Kenta Maeda", "Joc Pederson", "Alex Verdugo", "Chris Taylor", "Ross Stripling", "Will Smith", "Julio Ur\u00edas", "David Freese", "Pedro B\u00e1ez", "Kenley Jansen", "Russell Martin", "Rich Hill", "Dustin May", "Enrique Hern\u00e1ndez", "Tony Gonsolin", "Dylan Floro", "Austin Barnes", "AJ Pollock", "Matt Beaty", "Joe Kelly", "Edwin Rios", "Gavin Lux", "Kyle Garlick", "Kristopher Negr\u00f3n", "Adam Kolarek", "JT Chargois", "Casey Sadler", "Travis d\u0027Arnaud", "Brock Stewart", "Jaime Schultz", "Caleb Ferguson", "Dennis Santana", "Josh Sborz"], "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": [7.76, 5.27, 5.05, 4.38, 3.9, 3.56, 3.26, 3.11, 2.82, 2.17, 2.1, 1.94, 1.87, 1.67, 1.67, 1.4, 1.24, 1.07, 1.02, 0.9, 0.81, 0.8, 0.62, 0.61, 0.57, 0.43, 0.42, 0.38, 0.3, 0.22, 0.17, 0.16, 0.09, 0.04, -0.02, -0.05, -0.06, -0.06, -0.07, -0.08]}; 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);});