RRosternomics

Reds CIN

GM: Dan O'Brien

Overview

REBUILD → steady
cheap & losing · year 1 in this regime · payroll 0.81× league · TAB -1.2
-1.2TAB — talent above budget17th/30
-7.3WAB — realized wins (TAB + luck)25th/30
10.0depth — 2+ WAR regulars3rd/30
-7.0dead WAR — sub-replacement drag27th/30
53%dead money %28th/30
0.444win %23rd/30
30.5roster fWAR16th/30
-4surplus $M (value vs cost)14th/30
$67Mpayroll $M19th/30

Roster

CFJosh Hamilton 71GRyan Freel 59GNorris Hopper 55G
LFAdam Dunn 144GNorris Hopper 34GJason Ellison 32G
RFKen Griffey Jr. 133GJason Ellison 40GNorris Hopper 13G
SSÁlex González 103GJeff Keppinger 47GJuan Castro 16G
2BBrandon Phillips 156GJuan Castro 8GJeff Keppinger 3G
3BEdwin Encarnación 137GRyan Freel 19GJuan Castro 17G
1BScott Hatteberg 96GJorge Cantu 21GJoey Votto 17G
STARTERSAaron Harang 4.8WARBronson Arroyo 3.4WARMatt Belisle 2.0WARBobby Livingston 1.1WARHomer Bailey 0.6WAR
CDavid Ross 108GJavier Valentin 73GRyan Jorgensen 4G
DHAdam Dunn 3GKen Griffey Jr. 3GJeff Conine 1G
BULLPEN David Weathers 33svJared Burton 11hMike Stanton 10hJon Coutlangus 9hTodd Coffey 6hGary Majewski 6hKirk Saarloos 4hBrad Salmon 0h
CDavid Ross 108 · Javier Valentin 73 · Ryan Jorgensen 4
1BScott Hatteberg 96 · Jorge Cantu 21 · Joey Votto 17
2BBrandon Phillips 156 · Juan Castro 8 · Jeff Keppinger 3
3BEdwin Encarnación 137 · Ryan Freel 19 · Juan Castro 17
SSÁlex González 103 · Jeff Keppinger 47 · Juan Castro 16
LFAdam Dunn 144 · Norris Hopper 34 · Jason Ellison 32
CFJosh Hamilton 71 · Ryan Freel 59 · Norris Hopper 55
RFKen Griffey Jr. 133 · Jason Ellison 40 · Norris Hopper 13
DHAdam Dunn 3 · Ken Griffey Jr. 3 · Jeff Conine 1
SPAaron Harang 4.8 · Bronson Arroyo 3.4 · Matt Belisle 2.0 · Bobby Livingston 1.1 · Homer Bailey 0.6
BP David Weathers 33 · Jared Burton 11 · Mike Stanton 10 · Jon Coutlangus 9 · Todd Coffey 6 · Gary Majewski 6 · Kirk Saarloos 4 · Brad Salmon 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.

2007 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Aaron Harang 29 arb 354.8 4.2 +6.2
Brandon Phillips 26 arb 1584.4 0.4 +9.2
Bronson Arroyo 30 FA-elig 343.4 4.1 +3.1
Adam Dunn 27 FA-elig 1522.9 10.5 -4.3
Josh Hamilton 26 pre-arb 902.3 0.4 +4.5
Norris Hopper 28 1212.3
Álex González 30 FA-elig 1102.2 3.5 +1.2
Jeff Keppinger 27 arb 672.0 0.4 +4.0
Matt Belisle 27 arb 302.0 0.4 +4.0
Kyle Lohse↔ PHI 28 FA-elig 222.0 2.6 +1.7
Scott Hatteberg 37 FA-elig 1161.4 1.5 +1.6
David Weathers 37 FA-elig 701.3 2.2 +0.4
Edwin Encarnación 24 pre-arb 1391.2 0.4 +2.2
Bobby Livingston 24 111.1
Jason Ellison↔ SEA 29 arb 370.7 0.1 +1.4
Homer Bailey 21 90.6 0.4 +0.9
David Ross 30 arb 1120.5 1.6 -0.6
Eric Milton 31 FA-elig 60.4 10.3 -9.4
Jared Burton 26 pre-arb 470.4 0.4 +0.5
Mike Stanton 40 FA-elig 690.4 2.0 -1.2
Marcus McBeth 26 230.3
Bill Bray 24 pre-arb 190.3 0.4 +0.2
Jon Coutlangus 26 pre-arb 640.1 0.4 -0.1
Brad Salmon 27 260.1
Gary Majewski 27 320.1
Ryan Jorgensen 28 40.1
Dewayne Wise 29 50.1
Eddie Guardado 36 FA-elig 150.1 0.5 -0.4
Ryan Hanigan 26 50.0
Buck Coats 25 200.0
Jeff Conine↔ NYM 41 FA-elig 800.0 1.6 -1.6
Jerry Gil 24 arb 10.0 0.4 -0.4
Jorge Cantu↔ TBR 25 27-0.0
Javier Valentin 31 FA-elig 97-0.0 1.2 -1.3
Enrique Cruz 25 1-0.1
Joey Votto 23 24-0.1 0.1 -0.3
Mark Bellhorn 32 13-0.1
Rheal Cormier 40 FA-elig 6-0.2 2.2 -2.7
Elizardo Ramirez 24 arb 4-0.2 0.4 -0.8
Tom Shearn 29 7-0.3
Ricky Stone 32 5-0.3
Kirk Saarloos 28 arb 34-0.3 1.2 -1.9
Mike Gosling 26 23-0.3
Ryan Freel 31 FA-elig 75-0.4 2.3 -3.1
Victor Santos↔ BAL 30 32-0.4
Pedro Lopez 23 14-0.5
Phil Dumatrait 25 6-0.6
Ken Griffey Jr. 37 FA-elig 144-0.6 8.4 -9.7
Chad Moeller↔ LAD 32 FA-elig 30-0.6 0.6 -2.0
Todd Coffey 26 pre-arb 58-0.7 0.4 -1.8
Juan Castro 35 FA-elig 54-1.1 0.9 -3.4

Key additions (new vs 2006)

PlayerfWAR
Josh Hamilton2.3
Álex González2.2
Jeff Keppinger2.0
Bobby Livingston1.1
Jason Ellison0.7
Homer Bailey0.6

Key subtractions (gone from 2006)

Player2006 fWAR
Rich Aurilia3.0
Austin Kearns1.9
Felipe Lopez1.3
Chris Denorfia0.9
Brandon Claussen0.4
Chris Hammond0.2

Current & Historical Payroll

2007 payroll
$66.6M

What the front office was committed to as of 2007, 2007–2013. 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 (20)

PlayerAgeSvc2007200820092010201120122013
Adam Dunn276.000$10.5M$9.4MFREE AGENT
Eric Milton319.000$10.3MFREE AGENT
Ken Griffey Jr.3718.000$8.4M$5.9MFREE AGENT
Bronson Arroyo307.000$4.1M$4.6M$10.1M$11.6M$7.7M$12.0M$16.4M
Álex González309.000$3.5MFREE AGENT$3.3M
Kyle Lohse286.000$2.6MFREE AGENT
Ryan Freel316.000$2.3M$3.0MFREE AGENT
David Weathers3716.000$2.2M$3.3M$2.2MFREE AGENT
Rheal Cormier4016.000$2.2MFREE AGENT
Mike Stanton4018.000$2.0MFREE AGENT
Jeff Conine4117.000$1.6MFREE AGENT
Scott Hatteberg3712.000$1.5M$1.9MFREE AGENT
Javier Valentin3110.000$1.2MFREE AGENT
Juan Castro3512.000$925k$863kFREE AGENT
Chad Moeller327.000$568kFREE AGENT
Eddie Guardado3614.000$500kFREE AGENT
Mark Bellhorn3210.000FREE AGENT
Ricky Stone326.000FREE AGENT
Victor Santos306.000FREE AGENT
Dewayne Wise297.000FREE AGENT

Arbitration-eligible (13)

PlayerAgeSvc2007200820092010201120122013
Aaron Harang295.000$4.2MFREE AGENT
David Ross305.000$1.6MFREE AGENT
Kirk Saarloos285.000$1.2MFREE AGENT
Brandon Phillips265.000$408kFREE AGENT
Matt Belisle274.000$390kARB 3FREE AGENT
Elizardo Ramirez243.000$390kARB 2ARB 3FREE AGENT
Jeff Keppinger273.000$382kARB 2ARB 3FREE AGENT
Jerry Gil243.000$380kARB 2ARB 3FREE AGENT
Jason Ellison294.000$144kARB 3FREE AGENT
Gary Majewski273.000ARB 2ARB 3FREE AGENT
Jorge Cantu253.000ARB 2ARB 3FREE AGENT
Enrique Cruz254.000ARB 3FREE AGENT
Mike Gosling263.000ARB 2ARB 3FREE AGENT

Pre-arbitration (18)

PlayerAgeSvc2007200820092010201120122013
Edwin Encarnación242.000$408kARB 1ARB 2ARB 3FREE AGENT
Todd Coffey262.000$408kARB 1ARB 2ARB 3FREE AGENT
Bill Bray241.000$382kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Josh Hamilton260.000$380kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jared Burton260.000$380kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jon Coutlangus260.000$380kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Homer Bailey210.000$380kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Joey Votto230.000$56kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tom Shearn29
Ryan Jorgensen28
Phil Dumatrait250.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Pedro Lopez23
Norris Hopper281.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Brad Salmon27
Ryan Hanigan260.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Marcus McBeth26
Bobby Livingston24
Buck Coats251.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Reds 2007; 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.902, 2.45], [1986, 1.028, -1.73], [1987, 0.862, 3.35], [1988, 0.805, 7.3], [1989, 0.864, -5.28], [1990, 0.816, 9.71], [1991, 1.049, 5.04], [1992, 1.109, 5.92], [1993, 1.336, -10.65], [1994, 1.206, 13.15], [1995, 1.316, 10.77], [1996, 1.138, -8.19], [1997, 1.005, -10.92], [1998, 0.544, 4.12], [1999, 0.707, 14.14], [2000, 0.77, 7.06], [2001, 0.782, -11.87], [2002, 0.727, -2.5], [2003, 0.827, -15.04], [2004, 0.704, -8.93], [2005, 0.799, -1.32], [2006, 0.807, 2.83], [2007, 0.81, -1.23], [2008, 0.715, -7.55], [2009, 0.852, -5.08], [2010, 0.802, 16.6], [2011, 0.825, 3.63], [2012, 0.771, 10.95], [2013, 1.004, 6.1], [2014, 0.969, -6.36], [2015, 0.882, -5.44], [2016, 0.703, -17.24], [2017, 0.635, -1.64], [2018, 0.746, -4.92], [2019, 0.959, -1.32], [2021, 0.993, 1.18], [2022, 0.714, -15.33], [2023, 0.46, 3.94], [2024, 0.598, -1.93], [2025, 0.717, 2.23], [2026, 0.703, -11.63]], TEAMCOL = '#C6011F'; 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 = 2007; 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", "SP", "OF", "OF", "OF", "IF", "IF", "SP", "SP", "IF", "RP", "IF", "SP", "OF", "SP", "C", "SP", "RP", "RP", "RP", "RP", "RP", "RP", "RP", "C", "OF", "RP", "C", "OF", "IF", "IF", "IF", "C", "IF", "IF", "IF", "RP", "SP", "SP"], "league": [5.72, 4.58, 3.74, 3.11, 2.66, 2.43, 2.17, 1.81, 1.59, 1.45, 1.23, 1.1, 0.94, 0.85, 0.75, 0.67, 0.57, 0.49, 0.44, 0.37, 0.3, 0.27, 0.22, 0.18, 0.13, 0.1, 0.08, 0.05, 0.02, -0.0, -0.03, -0.05, -0.07, -0.09, -0.12, -0.15, -0.18, -0.23, -0.29, -0.33], "names": ["Aaron Harang", "Brandon Phillips", "Bronson Arroyo", "Adam Dunn", "Josh Hamilton", "Norris Hopper", "\u00c1lex Gonz\u00e1lez", "Jeff Keppinger", "Matt Belisle", "Kyle Lohse", "Scott Hatteberg", "David Weathers", "Edwin Encarnaci\u00f3n", "Bobby Livingston", "Jason Ellison", "Homer Bailey", "David Ross", "Eric Milton", "Jared Burton", "Mike Stanton", "Marcus McBeth", "Bill Bray", "Jon Coutlangus", "Brad Salmon", "Gary Majewski", "Ryan Jorgensen", "Dewayne Wise", "Eddie Guardado", "Ryan Hanigan", "Buck Coats", "Jeff Conine", "Jerry Gil", "Jorge Cantu", "Javier Valentin", "Enrique Cruz", "Joey Votto", "Mark Bellhorn", "Rheal Cormier", "Elizardo Ramirez", "Tom Shearn"], "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.85, 4.44, 3.37, 2.89, 2.26, 2.25, 2.17, 2.04, 2.02, 2.01, 1.44, 1.25, 1.2, 1.1, 0.7, 0.6, 0.48, 0.43, 0.42, 0.36, 0.27, 0.27, 0.14, 0.1, 0.1, 0.07, 0.07, 0.06, 0.05, 0.04, 0.01, 0.0, -0.02, -0.04, -0.09, -0.11, -0.12, -0.2, -0.21, -0.34]}; 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:'CIN',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);});