RRosternomics

Pirates PIT

GM: Neal Huntington

Overview

WINDOW ↗ rising
cheap & winning · year 2 in this regime · payroll 0.72× league · TAB +9.2
+9.2TAB — talent above budget3rd/30
+10.2WAB — realized wins (TAB + luck)4th/30
6.0depth — 2+ WAR regulars15th/30
-5.4dead WAR — sub-replacement drag13th/30
52%dead money %26th/30
0.543win %8th/30
39.6roster fWAR6th/30
+36surplus $M (value vs cost)3rd/30
$82Mpayroll $M23rd/30

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

Roster

CFAndrew McCutchen 146GStarling Marte 28GMichael Martinez 2G
LFStarling Marte 114GTravis Snider 36GJosh Harrison 26G
RFGregory Polanco 83GTravis Snider 64GJose Tabata 37G
SSJordy Mercer 144GClint Barmes 27GJosh Harrison 8G
2BNeil Walker 135GJosh Harrison 17GClint Barmes 14G
3BPedro Álvarez 99GJosh Harrison 72GBrent Morel 13G
1BIke Davis 124GGaby Sanchez 96GPedro Álvarez 5G
STARTERSGerrit Cole 2.4WARFrancisco Liriano 1.6WARVance Worley 1.2WARCharlie Morton 1.0WARBrandon Cumpton 0.9WAR
CRussell Martin 107GChris Stewart 46GTony Sanchez 20G
DHGaby Sanchez 3GPedro Álvarez 3GIke Davis 2G
BULLPEN Mark Melancon 33svTony Watson 34hJustin Wilson 16hJared Hughes 14hErnesto Frieri 3hJohn Axford 2hJeanmar Gómez 2hStolmy Pimentel 0h
CRussell Martin 107 · Chris Stewart 46 · Tony Sanchez 20
1BIke Davis 124 · Gaby Sanchez 96 · Pedro Álvarez 5
2BNeil Walker 135 · Josh Harrison 17 · Clint Barmes 14
3BPedro Álvarez 99 · Josh Harrison 72 · Brent Morel 13
SSJordy Mercer 144 · Clint Barmes 27 · Josh Harrison 8
LFStarling Marte 114 · Travis Snider 36 · Josh Harrison 26
CFAndrew McCutchen 146 · Starling Marte 28 · Michael Martinez 2
RFGregory Polanco 83 · Travis Snider 64 · Jose Tabata 37
DHGaby Sanchez 3 · Pedro Álvarez 3 · Ike Davis 2
SPGerrit Cole 2.4 · Francisco Liriano 1.6 · Vance Worley 1.2 · Charlie Morton 1.0 · Brandon Cumpton 0.9
BP Mark Melancon 33 · Tony Watson 34 · Justin Wilson 16 · Jared Hughes 14 · Ernesto Frieri 3 · John Axford 2 · Jeanmar Gómez 2 · Stolmy Pimentel 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.

2014 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Andrew McCutchen 27 arb 1467.4 7.2 +14.4
Russell Martin 31 FA-elig 1116.1 8.5 +9.5
Josh Harrison 26 arb 1434.8 0.5 +13.5
Starling Marte 25 pre-arb 1354.5 0.5 +12.7
Neil Walker 28 arb 1374.1 5.8 +6.2
Gerrit Cole 23 pre-arb 232.4 0.5 +6.5
Jordy Mercer 27 pre-arb 1492.0 0.5 +5.3
Mark Melancon 29 arb 721.9 2.6 +3.0
Travis Snider 26 FA-elig 1401.6 1.2 +3.4
Francisco Liriano 30 FA-elig 291.6 6.0 -1.5
Chris Stewart 32 FA-elig 491.5 1.0 +3.5
Tony Watson 29 arb 781.4 0.5 +3.5
Vance Worley 26 pre-arb 201.2 0.3 +3.3
Charlie Morton 30 FA-elig 261.0 4.0 -1.0
Brandon Cumpton 25 pre-arb 160.9 0.5 +2.1
Gregory Polanco 22 890.5 0.3 +1.1
Edinson Volquez 30 FA-elig 320.3 5.0 -4.0
Clint Barmes 35 FA-elig 480.3 2.0 -1.1
Pedro Álvarez 27 arb 1220.3 4.2 -3.3
Ike Davis↔ NYM 27 arb 1310.2 3.2 -2.5
John Holdzkom 26 90.2 0.5 +0.1
Travis Ishikawa↔ SFG 30 150.2 0.0 +0.5
Justin Wilson 26 pre-arb 700.1 0.5 -0.1
Tony Sanchez 26 pre-arb 260.1 0.5 -0.1
Jeff Locke 26 arb 210.1 0.5 -0.3
John Axford↔ CLE 31 arb 130.1 0.9 -0.8
Andrew Lambo 25 pre-arb 210.0 0.1 +0.0
Gaby Sanchez 30 FA-elig 1230.0 2.3 -2.2
Casey Sadler 23 arb 6-0.0 0.1 -0.1
Bobby LaFromboise 28 pre-arb 6-0.1 0.1 -0.2
Chase d'Arnaud 27 8-0.1
Matt Hague 28 3-0.1
Jaff Decker 24 pre-arb 5-0.1 0.0 -0.4
Stolmy Pimentel 24 pre-arb 20-0.2 0.5 -1.1
Brent Morel 27 pre-arb 23-0.2 0.1 -0.8
Vin Mazzaro 27 arb 5-0.2 0.9 -1.7
Ernesto Frieri↔ ANA 28 arb 14-0.3 1.1 -1.9
Jared Hughes 28 pre-arb 63-0.4 0.4 -1.5
Jeanmar Gómez 26 arb 44-0.4 0.5 -1.6
Michael Martinez 31 26-0.4
Jose Tabata 25 arb 80-0.4 3.0 -4.3
Jayson Nix↔ KCR 31 FA-elig 16-0.5 0.4 -1.7
Jason Grilli↔ ANA 37 FA-elig 22-0.5 1.5 -3.0
Wandy Rodriguez 35 FA-elig 6-0.7 13.0 -15.2
Bryan Morris↔ MIA 27 pre-arb 21-0.8 0.2 -2.5

Key additions (new vs 2013)

PlayerfWAR
Chris Stewart1.5
Vance Worley1.2
Gregory Polanco0.5
Edinson Volquez0.3
Ike Davis0.2
John Holdzkom0.2

Key subtractions (gone from 2013)

Player2013 fWAR
A.J. Burnett3.6
Marlon Byrd0.7
James McDonald0.3
Kris Johnson0.1
Alex Presley0.1
Kyle Farnsworth0.1

Current & Historical Payroll

2014 payroll
$81.7M

What the front office was committed to as of 2014, 2014–2020. 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 (6)

PlayerAgeSvc2014201520162017201820192020
Wandy Rodriguez358.105$13.0MFREE AGENT
Russell Martin317.150$8.5MFREE AGENT
Francisco Liriano307.104$6.0M$11.7M$9.5MFREE AGENT$100k
Edinson Volquez306.059$5.0MFREE AGENT
Clint Barmes358.112$2.0MFREE AGENT
Jason Grilli377.016$1.5MFREE AGENT

Arbitration-eligible (17)

PlayerAgeSvc2014201520162017201820192020
Andrew McCutchen274.123$7.2MARB 3FREE AGENT
Neil Walker283.166$5.8MARB 2ARB 3FREE AGENT
Pedro Álvarez273.085$4.2MARB 2ARB 3FREE AGENT
Charlie Morton305.009$4.0MFREE AGENT
Ike Davis273.155$3.2MARB 2ARB 3FREE AGENT
Jose Tabata254.000$3.0MARB 3FREE AGENT
Mark Melancon293.098$2.6MARB 2ARB 3FREE AGENT
Gaby Sanchez304.025$2.3MARB 3FREE AGENT
Travis Snider263.091$1.2MARB 2ARB 3FREE AGENT
Ernesto Frieri285.000$1.1MFREE AGENT
Chris Stewart323.091$1.0MARB 2ARB 3FREE AGENT
Vin Mazzaro273.021$950kARB 2ARB 3FREE AGENT
John Axford313.170$944kARB 2ARB 3FREE AGENT
Jayson Nix314.127$371kARB 3FREE AGENT
Casey Sadler234.035$126kARB 3FREE AGENT
Travis Ishikawa303.098$41kARB 2ARB 3FREE AGENT
Chase d'Arnaud273.045ARB 2ARB 3FREE AGENT

Pre-arbitration (22)

PlayerAgeSvc2014201520162017201820192020
Tony Watson292.101$518kARB 1ARB 2ARB 3FREE AGENT
Jordy Mercer271.095$516kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Justin Wilson261.035$515kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jeanmar Gómez262.063$514kARB 1ARB 2ARB 3FREE AGENT
Jeff Locke261.068$513kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Josh Harrison262.033$513kARB 1ARB 2ARB 3FREE AGENT
Gerrit Cole230.111$512kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tony Sanchez260.155$507kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Brandon Cumpton250.127$507kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Stolmy Pimentel241.030$505kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Starling Marte251.070$500kPre-ArbARB 1ARB 2ARB 3FREE AGENT
John Holdzkom26$500k
Jared Hughes282.007$418kARB 1ARB 2ARB 3FREE AGENT
Vance Worley262.033$306kARB 1ARB 2ARB 3FREE AGENT
Gregory Polanco220.000$281kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Bryan Morris271.011$179kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Brent Morel271.151$123kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Andrew Lambo251.079$85kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Bobby LaFromboise280.111$71kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jaff Decker240.098$22kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Michael Martinez312.148ARB 1ARB 2ARB 3FREE AGENT
Matt Hague280.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Pirates 2014; 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, 0.994, -10.2], [1986, 0.716, 0.52], [1987, 0.559, 12.18], [1988, 0.541, 10.89], [1989, 0.851, -1.23], [1990, 0.833, 13.44], [1991, 1.005, 13.53], [1992, 1.147, 1.31], [1993, 0.665, -1.3], [1994, 0.69, -7.9], [1995, 0.54, -6.8], [1996, 0.666, -7.78], [1997, 0.273, 2.07], [1998, 0.344, 1.55], [1999, 0.486, 3.48], [2000, 0.524, -2.74], [2001, 0.742, -14.99], [2002, 0.565, -7.14], [2003, 0.659, 0.8], [2004, 0.449, -1.06], [2005, 0.504, -5.41], [2006, 0.532, -6.45], [2007, 0.54, -3.47], [2008, 0.535, -9.72], [2009, 0.459, -1.65], [2010, 0.395, -15.14], [2011, 0.561, -11.48], [2012, 0.686, -6.18], [2013, 0.767, 8.23], [2014, 0.721, 9.24], [2015, 0.841, 11.31], [2016, 0.812, -0.52], [2017, 0.75, -3.64], [2018, 0.664, 4.59], [2019, 0.502, -3.88], [2021, 0.371, -9.98], [2022, 0.391, -8.07], [2023, 0.429, 0.89], [2024, 0.604, -7.07], [2025, 0.488, 0.03], [2026, 0.672, 14.53]], TEAMCOL = '#C9A227'; 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 = 2014; 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", "C", "IF", "OF", "IF", "SP", "IF", "RP", "OF", "SP", "C", "RP", "SP", "SP", "SP", "OF", "SP", "IF", "IF", "IF", "RP", "IF", "RP", "C", "SP", "RP", "OF", "IF", "RP", "RP", "IF", "IF", "OF", "RP", "IF", "RP", "RP", "RP", "RP", "OF"], "league": [5.68, 4.42, 3.76, 3.14, 2.78, 2.45, 2.1, 1.92, 1.69, 1.49, 1.31, 1.16, 1.03, 0.94, 0.79, 0.66, 0.56, 0.49, 0.43, 0.38, 0.32, 0.28, 0.23, 0.18, 0.13, 0.09, 0.07, 0.04, 0.02, -0.0, -0.03, -0.05, -0.07, -0.1, -0.12, -0.15, -0.18, -0.21, -0.28, -0.27], "names": ["Andrew McCutchen", "Russell Martin", "Josh Harrison", "Starling Marte", "Neil Walker", "Gerrit Cole", "Jordy Mercer", "Mark Melancon", "Travis Snider", "Francisco Liriano", "Chris Stewart", "Tony Watson", "Vance Worley", "Charlie Morton", "Brandon Cumpton", "Gregory Polanco", "Edinson Volquez", "Clint Barmes", "Pedro \u00c1lvarez", "Ike Davis", "John Holdzkom", "Travis Ishikawa", "Justin Wilson", "Tony Sanchez", "Jeff Locke", "John Axford", "Andrew Lambo", "Gaby Sanchez", "Casey Sadler", "Bobby LaFromboise", "Chase d\u0027Arnaud", "Matt Hague", "Jaff Decker", "Stolmy Pimentel", "Brent Morel", "Vin Mazzaro", "Ernesto Frieri", "Jared Hughes", "Jeanmar G\u00f3mez", "Michael Martinez"], "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.4, 6.14, 4.8, 4.5, 4.08, 2.38, 1.97, 1.92, 1.58, 1.55, 1.54, 1.37, 1.23, 1.03, 0.91, 0.48, 0.35, 0.32, 0.31, 0.25, 0.2, 0.19, 0.13, 0.12, 0.09, 0.06, 0.04, 0.02, -0.0, -0.06, -0.08, -0.09, -0.12, -0.19, -0.22, -0.25, -0.27, -0.36, -0.38, -0.42]}; 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:'PIT',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);});