RRosternomics

Brewers MIL

GM: Matt Arnold

Overview

WINDOW ↗ rising
cheap & winning · year 10 in this regime · payroll 0.71× league · TAB +17.9
+17.9TAB — talent above budget1st/30
+14.6WAB — realized wins (TAB + luck)1st/30
4.0depth — 2+ WAR regulars7th/30
-2.9dead WAR — sub-replacement drag8th/30
34%dead money %12th/30
0.621win %2nd/30
48.0roster fWAR2nd/30
+187surplus $M (value vs cost)4th/30
$113Mpayroll $M19th/30luxury tax $117M

Roster

CFGarrett Mitchell 55GBlake Perkins 23GJackson Chourio 10G
LFJackson Chourio 31GBrandon Lockridge 26GJake Bauers 11G
RFSal Frelick 61GBlake Perkins 7GLuis Matos 5G
SSJoey Ortiz 58GDavid Hamilton 29GBrice Turang 1G
2BBrice Turang 64GDavid Hamilton 7GLuis Rengifo 1G
3BLuis Rengifo 51GDavid Hamilton 31GAndrew Vaughn 1G
1BJake Bauers 50GAndrew Vaughn 22GGary Sánchez 6G
STARTERSJacob Misiorowski 4.6WARKyle Harrison 2.2WARBrandon Woodruff 1.2WARLogan Henderson 1.1WARChad Patrick 1.0WAR
CWilliam Contreras 57GGary Sánchez 16GJeferson Quero 1G
DHChristian Yelich 35GGary Sánchez 13GWilliam Contreras 8G
BULLPEN Trevor Megill 8svAbner Uribe 7hGrant Anderson 5hJoel Kuhnel 4hDL Hall 3hAaron Ashby 2hShane Drohan 2hJake Woodford 0h
CWilliam Contreras 57 · Gary Sánchez 16 · Jeferson Quero 1
1BJake Bauers 50 · Andrew Vaughn 22 · Gary Sánchez 6
2BBrice Turang 64 · David Hamilton 7 · Luis Rengifo 1
3BLuis Rengifo 51 · David Hamilton 31 · Andrew Vaughn 1
SSJoey Ortiz 58 · David Hamilton 29 · Brice Turang 1
LFJackson Chourio 31 · Brandon Lockridge 26 · Jake Bauers 11
CFGarrett Mitchell 55 · Blake Perkins 23 · Jackson Chourio 10
RFSal Frelick 61 · Blake Perkins 7 · Luis Matos 5
DHChristian Yelich 35 · Gary Sánchez 13 · William Contreras 8
SPJacob Misiorowski 4.6 · Kyle Harrison 2.2 · Brandon Woodruff 1.2 · Logan Henderson 1.1 · Chad Patrick 1.0
BP Trevor Megill 8 · Abner Uribe 7 · Grant Anderson 5 · Joel Kuhnel 4 · DL Hall 3 · Aaron Ashby 2 · Shane Drohan 2 · Jake Woodford 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
Jacob Misiorowski 24 pre-arb 197.3 0.8 +45.1
Brice Turang 26 pre-arb 975.8 4.2 +32.4
Garrett Mitchell 27 arb 933.5 0.9 +21.1
Kyle Harrison 24 pre-arb 173.5 0.8 +21.0
Shane Drohan 27 213.0 0.7 +18.2
Jackson Chourio 22 pre-arb 692.9 7.2 +11.2
Trevor Megill 32 arb 402.8 4.7 +12.9
Jake Bauers 30 arb 942.7 2.7 +14.3
William Contreras 28 arb 962.6 9.4 +6.7
Brandon Woodruff 33 FA-elig 92.0 22.0 -9.7
Joey Ortiz 27 pre-arb 901.9 0.8 +11.4
Logan Henderson 24 pre-arb 81.8 0.6 +10.6
Aaron Ashby 28 arb 481.7 5.7 +5.3
Andrew Vaughn 28 arb 611.7 7.7 +2.8
Chad Patrick 27 pre-arb 321.6 0.8 +9.1
Abner Uribe 26 pre-arb 421.4 0.8 +8.2
Gary Sánchez 33 FA-elig 571.2 1.8 +6.0
Cooper Pratt 21 301.0 2.9 +3.2
Christian Yelich 34 FA-elig 680.6 24.0 -20.5
Brandon Lockridge 29 pre-arb 280.5 0.8 +2.1
Grant Anderson 29 pre-arb 410.4 0.8 +1.9
Robert Gasser 27 pre-arb 100.4 0.0 +2.5
DL Hall 27 pre-arb 240.3 0.8 +1.3
Coleman Crow 25 50.3 0.5 +1.5
David Hamilton 28 pre-arb 750.3 0.8 +1.1
Brandon Sproat 25 pre-arb 190.2 0.8 +0.7
Tyler Black 25 pre-arb 90.2 0.1 +1.2
Brian Fitzpatrick 26 50.2 0.1 +1.1
Jake Woodford↔ CHC 29 arb 160.1 0.9 -0.0
Lance McCullers Jr.↔ HOU 32 10.1 2.0 -1.1
Bryse Wilson↔ CHC 28 30.1
Carlos Rodriguez 24 40.1
Garrett Stallings 28 20.1
Drew Rom 26 50.1 0.5 +0.0
Braden Shewmake↔ HOU 28 40.1 0.1 +0.4
Luis Lara 21 110.1
Jeferson Quero 23 10.0 0.0 +0.2
Easton McGee 28 pre-arb 20.0 0.0 +0.1
Luis Matos 24 pre-arb 9-0.1 0.1 -0.5
Craig Yoho 26 pre-arb 13-0.1 0.8 -1.6
Jared Koenig 32 pre-arb 9-0.2 0.8 -2.0
Angel Zerpa 26 arb 12-0.2 1.1 -2.5
Sal Frelick 26 pre-arb 90-0.3 0.8 -2.9
Greg Jones 28 pre-arb 12-0.8 0.1 -5.0
Joel Kuhnel↔ OAK 31 10-0.9 0.2 -5.7
Blake Perkins 29 pre-arb 54-0.9 0.8 -6.4
Luis Rengifo↔ SDP 29 FA-elig 57-1.1 2.0 -8.7

Key additions (new vs 2025)

PlayerfWAR
Kyle Harrison2.2
Shane Drohan1.9
Gary Sánchez0.8
Coleman Crow0.2
David Hamilton0.2
Brandon Sproat0.2

Key subtractions (gone from 2025)

Player2025 fWAR
Freddy Peralta3.6
Caleb Durbin2.6
Isaac Collins2.6
Andruw Monasterio0.7
Nick Mears0.5
Tyler Alexander0.4

Current & Historical Payroll

2026 payroll
$113.2M
luxury tax $117M
2027 guaranteed
$66.1M

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$72M$41M$38M$41M$10M$10M
Proj. arbitration$26M$9M
Proj. pre-arbitration$19M
Player benefits$18M$18M$18M$18M$18M$18M
Total CBT payroll$135M$69M$56M$59M$28M$28M
Tax space+$109M+$178M+$194M+$194M+$228M+$231M
Est. tax bill

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

Guaranteed / signed (8)

PlayerAgeSvc2026202720282029203020312032
Christian Yelich3412.069$24.0M$23.9M$23.9M$20.0MMUTUALFREE AGENT
Brandon Woodruff337.161$22.0MFREE AGENT
William Contreras284.112$9.4M$14.5MCLUBFREE AGENT
Jackson Chourio222.000$7.2M$8.2M$9.2M$15.2M$16.2M$17.2M$25.0MCLUB
Aaron Ashby283.088$5.7M$7.7M$9.0MCLUB$13.0MCLUBFREE AGENT
Lance McCullers Jr.3210.140$2.0MFREE AGENT
Luis Rengifo296.043$2.0M$10.0MMUTUALFREE AGENT
Gary Sánchez339.050$1.8M$1.8MFREE AGENT

Arbitration-eligible (7)

PlayerAgeSvc2026202720282029203020312032
Andrew Vaughn284.142$7.7MARB 3FREE AGENT
Trevor Megill324.002$4.7MARB 3FREE AGENT
Jake Bauers305.084$2.7MFREE AGENT
Angel Zerpa263.082$1.1MARB 2ARB 3FREE AGENT
Garrett Mitchell273.040$950kARB 2ARB 3FREE AGENT
Jake Woodford294.008$941kARB 3FREE AGENT
Bryse Wilson285.036FREE AGENT

Pre-arbitration (29)

PlayerAgeSvc2026202720282029203020312032
Brice Turang262.165$4.2MARB 1ARB 2ARB 3FREE AGENT
Sal Frelick262.072$801kARB 1ARB 2ARB 3FREE AGENT
Abner Uribe261.121$798kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Chad Patrick270.125$796kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jared Koenig322.043$796kARB 1ARB 2ARB 3FREE AGENT
Grant Anderson292.007$793kARB 1ARB 2ARB 3FREE AGENT
Joey Ortiz272.030$790kARB 1ARB 2ARB 3FREE AGENT
Jacob Misiorowski240.109$788kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
DL Hall272.074$788kARB 1ARB 2ARB 3FREE AGENT
David Hamilton282.011$787kARB 1ARB 2ARB 3FREE AGENT
Kyle Harrison241.102$786kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Brandon Lockridge290.147$783kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Brandon Sproat250.022$783kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Craig Yoho260.042$782kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Blake Perkins292.133$759kARB 1ARB 2ARB 3FREE AGENT
Shane Drohan27$697k
Logan Henderson240.075$613kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Coleman Crow25$526k
Drew Rom262.044$471kARB 1ARB 2ARB 3FREE AGENT
Joel Kuhnel311.127$206kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Luis Matos241.156$129kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Greg Jones280.037$83kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Braden Shewmake280.065$79kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tyler Black250.040$75kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Brian Fitzpatrick26$67k
Easton McGee281.024$38kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Robert Gasser271.136$31kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jeferson Quero23$13k
Carlos Rodriguez242.000ARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Brewers 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.151, -18.78], [1986, 0.779, -7.23], [1987, 0.66, 8.49], [1988, 0.788, 2.37], [1989, 0.833, -5.67], [1990, 1.156, -8.35], [1991, 0.988, -5.13], [1992, 0.89, 2.89], [1993, 0.755, -7.96], [1994, 0.75, -4.09], [1995, 0.542, -5.69], [1996, 0.616, -0.67], [1997, 0.646, -1.44], [1998, 0.8, -6.53], [1999, 0.9, -7.8], [2000, 0.577, -5.65], [2001, 0.701, -8.81], [2002, 0.656, -15.0], [2003, 0.451, -8.36], [2004, 0.41, 5.49], [2005, 0.585, 8.07], [2006, 0.808, 0.48], [2007, 0.814, 12.2], [2008, 0.975, 5.21], [2009, 0.915, -3.57], [2010, 0.829, 5.15], [2011, 0.983, 13.46], [2012, 0.963, 8.32], [2013, 0.753, -3.44], [2014, 0.931, -2.58], [2015, 0.731, -7.97], [2016, 0.531, 0.38], [2017, 0.544, 12.47], [2018, 0.779, 11.09], [2019, 0.952, 1.91], [2021, 0.755, 10.63], [2022, 0.916, 6.16], [2023, 0.872, 1.91], [2024, 0.732, 9.96], [2025, 0.698, 16.91], [2026, 0.709, 17.92]], TEAMCOL = '#FFC52F'; 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": ["SP", "IF", "OF", "SP", "RP", "OF", "RP", "IF", "C", "SP", "IF", "SP", "RP", "IF", "RP", "RP", "C", "IF", "OF", "OF", "RP", "SP", "RP", "SP", "IF", "SP", "IF", "RP", "RP", "SP", "RP", "RP", "RP", "RP", "IF", "OF", "C", "RP", "OF", "RP"], "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": ["Jacob Misiorowski", "Brice Turang", "Garrett Mitchell", "Kyle Harrison", "Shane Drohan", "Jackson Chourio", "Trevor Megill", "Jake Bauers", "William Contreras", "Brandon Woodruff", "Joey Ortiz", "Logan Henderson", "Aaron Ashby", "Andrew Vaughn", "Chad Patrick", "Abner Uribe", "Gary S\u00e1nchez", "Cooper Pratt", "Christian Yelich", "Brandon Lockridge", "Grant Anderson", "Robert Gasser", "DL Hall", "Coleman Crow", "David Hamilton", "Brandon Sproat", "Tyler Black", "Brian Fitzpatrick", "Jake Woodford", "Lance McCullers Jr.", "Bryse Wilson", "Carlos Rodriguez", "Garrett Stallings", "Drew Rom", "Braden Shewmake", "Luis Lara", "Jeferson Quero", "Easton McGee", "Luis Matos", "Craig Yoho"], "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.63, 3.69, 2.23, 2.2, 1.91, 1.86, 1.78, 1.71, 1.63, 1.24, 1.23, 1.14, 1.11, 1.05, 1.0, 0.91, 0.78, 0.62, 0.35, 0.29, 0.27, 0.25, 0.21, 0.21, 0.19, 0.15, 0.13, 0.11, 0.09, 0.08, 0.08, 0.07, 0.05, 0.05, 0.04, 0.04, 0.02, 0.01, -0.04, -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:'MIL',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);});