RRosternomics

Rangers TEX

President of baseball ops: Jon Daniels

Overview

REBUILD ↘ cooling
cheap & losing · year 1 in this regime · payroll 0.93× league · TAB -12.9
-12.9TAB — talent above budget25th/30
-14.0WAB — realized wins (TAB + luck)25th/30
5.0depth — 2+ WAR regulars21st/30
-5.9dead WAR — sub-replacement drag24th/30
25%dead money %5th/30
0.414win %23rd/30
20.5roster fWAR25th/30
-52surplus $M (value vs cost)24th/30
$122Mpayroll $M17th/30

Roster

CFDelino DeShields 102GCarlos Tocci 47GDrew Robinson 22G
LFJoey Gallo 85GRyan Rua 38GWillie Calhoun 27G
RFNomar Mazara 113GShin-Soo Choo 34GJoey Gallo 16G
SSElvis Andrus 97GJurickson Profar 68GHanser Alberto 5G
2BRougned Odor 127GIsiah Kiner-Falefa 20GJurickson Profar 10G
3BAdrian Beltré 70GJurickson Profar 51GIsiah Kiner-Falefa 46G
1BRonald Guzmán 117GJoey Gallo 35GJurickson Profar 24G
STARTERSMike Minor 2.6WARYovani Gallardo 0.5WARDoug Fister 0.4WARBartolo Colon 0.4WARAriel Jurado 0.3WAR
CRobinson Chirinos 108GIsiah Kiner-Falefa 35GCarlos Pérez 25G
DHShin-Soo Choo 85GAdrian Beltré 44GNomar Mazara 12G
BULLPEN José Leclerc 12svAlex Claudio 14hChris Martin 14hMatt Bush 3hKevin Jepsen 3hEddie Butler 2hJeffrey Springs 2hTony Barnette 1h
CRobinson Chirinos 108 · Isiah Kiner-Falefa 35 · Carlos Pérez 25
1BRonald Guzmán 117 · Joey Gallo 35 · Jurickson Profar 24
2BRougned Odor 127 · Isiah Kiner-Falefa 20 · Jurickson Profar 10
3BAdrian Beltré 70 · Jurickson Profar 51 · Isiah Kiner-Falefa 46
SSElvis Andrus 97 · Jurickson Profar 68 · Hanser Alberto 5
LFJoey Gallo 85 · Ryan Rua 38 · Willie Calhoun 27
CFDelino DeShields 102 · Carlos Tocci 47 · Drew Robinson 22
RFNomar Mazara 113 · Shin-Soo Choo 34 · Joey Gallo 16
DHShin-Soo Choo 85 · Adrian Beltré 44 · Nomar Mazara 12
SPMike Minor 2.6 · Yovani Gallardo 0.5 · Doug Fister 0.4 · Bartolo Colon 0.4 · Ariel Jurado 0.3
BP José Leclerc 12 · Alex Claudio 14 · Chris Martin 14 · Matt Bush 3 · Kevin Jepsen 3 · Eddie Butler 2 · Jeffrey Springs 2 · Tony Barnette 1

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.

2018 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
José Leclerc 24 pre-arb 592.6 0.5 +8.4
Mike Minor 30 FA-elig 282.6 8.3 +0.5
Rougned Odor 24 arb 1292.4 3.3 +4.8
Jurickson Profar 25 FA-elig 1462.2 1.1 +6.5
Joey Gallo 24 arb 1482.2 0.6 +6.8
Shin-Soo Choo 35 FA-elig 1461.9 20.0 -13.4
Adrian Beltré 39 FA-elig 1191.2 18.0 -14.1
Keone Kela↔ PIT 25 arb 381.1 0.8 +3.0
Alex Claudio 26 arb 661.0 0.6 +2.7
Robinson Chirinos 34 FA-elig 1130.9 2.2 +0.7
Elvis Andrus 29 FA-elig 970.8 15.2 -12.5
Jake Diekman↔ ARI 31 FA-elig 470.7 1.8 +0.7
Chris Martin 32 arb 460.7 1.8 +0.5
Delino DeShields 25 arb 1060.6 0.6 +1.5
Cole Hamels↔ CHC 34 FA-elig 200.6 14.7 -12.6
Nomar Mazara 23 pre-arb 1280.6 0.6 +1.3
Yovani Gallardo↔ CIN 32 FA-elig 180.5 1.0 +0.8
Isiah Kiner-Falefa 23 1110.5 0.5 +1.1
Ronald Guzmán 23 1230.4 0.5 +0.9
Doug Fister 34 FA-elig 120.4 3.5 -2.2
Bartolo Colon 45 FA-elig 280.4 1.8 -0.4
Cory Gearrin↔ OAK 32 FA-elig 210.4 0.6 +0.6
Tony Barnette 34 pre-arb 220.3 1.5 -0.4
Ariel Jurado 22 120.3 0.2 +0.9
Jeffrey Springs 25 180.2 0.2 +0.6
Jesse Chavez↔ CHC 34 FA-elig 300.2 0.5 +0.2
Austin Bibens-Dirkx 33 130.2 0.2 +0.5
Matt Moore 29 FA-elig 390.2 9.0 -8.4
Ricardo Rodríguez 25 pre-arb 40.1 0.2 +0.3
CD Pelham 23 100.1 0.1 +0.3
Nick Gardewine 24 pre-arb 30.1 0.1 +0.2
Yohander Méndez 23 pre-arb 80.0 0.1 +0.0
Connor Sadzeck 26 13-0.0 0.1 -0.1
Adrian Sampson 26 pre-arb 5-0.0 0.1 -0.1
Jose Trevino 25 3-0.1 0.0 -0.2
Brandon Mann 34 7-0.1 0.2 -0.5
Martín Pérez 27 FA-elig 22-0.1 6.0 -6.3
Matt Bush 32 pre-arb 21-0.1 0.6 -0.9
Zac Curtis↔ PHI 25 pre-arb 8-0.1 0.0 -0.4
Hanser Alberto 25 pre-arb 13-0.2 0.2 -0.7
Drew Hutchison↔ PHI 27 FA-elig 5-0.3 0.9 -1.8
Drew Robinson 26 pre-arb 47-0.3 0.3 -1.4
Juan Centeno 28 arb 10-0.5 0.6 -2.2
Renato Núñez↔ BAL 24 13-0.5 0.0 -1.6
Carlos Pérez↔ ATL 27 pre-arb 20-0.5 0.2 -2.0
Carlos Tocci 22 pre-arb 66-0.6 0.5 -2.5
Kevin Jepsen 33 21-0.6 0.4 -2.4
Willie Calhoun 23 pre-arb 35-0.6 0.2 -2.2
Ryan Rua 28 arb 61-0.6 0.9 -2.9
Eddie Butler↔ CHC 27 arb 22-0.9 0.4 -3.4

Key additions (new vs 2017)

PlayerfWAR
Mike Minor2.6
Chris Martin0.7
Yovani Gallardo0.5
Isiah Kiner-Falefa0.5
Ronald Guzmán0.4
Doug Fister0.4

Key subtractions (gone from 2017)

Player2017 fWAR
Yu Darvish2.7
Andrew Cashner2.2
Carlos Gómez2.0
Nick Martinez0.3
Jason Grilli0.2
Ernesto Frieri0.1

Current & Historical Payroll

2018 payroll
$121.7M
2019 guaranteed
$0M

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

PlayerAgeSvc2018201920202021202220232024
Shin-Soo Choo3510.119$20.0MFREE AGENT
Adrian Beltré3919.096$18.0MFREE AGENT
Elvis Andrus299.000$15.2MFREE AGENT
Cole Hamels3411.143$14.7MFREE AGENT
Matt Moore296.000$9.0MFREE AGENT
Mike Minor306.138$8.3MFREE AGENT
Doug Fister347.158$3.5MFREE AGENT
Bartolo Colon4519.059$1.8MFREE AGENT
Yovani Gallardo3210.108$977kFREE AGENT
Jesse Chavez347.108$533kFREE AGENT
Kevin Jepsen337.163$363kFREE AGENT

Arbitration-eligible (9)

PlayerAgeSvc2018201920202021202220232024
Martín Pérez275.038$6.0MFREE AGENT
Rougned Odor243.121$3.3MARB 2ARB 3FREE AGENT
Robinson Chirinos345.102$2.2MFREE AGENT
Jake Diekman315.050$1.8MFREE AGENT
Jurickson Profar253.165$1.1MARB 2ARB 3FREE AGENT
Drew Hutchison273.165$938kARB 2ARB 3FREE AGENT
Keone Kela253.000$844kARB 2ARB 3FREE AGENT
Cory Gearrin324.136$567kARB 3FREE AGENT
Ronald Guzmán234.090$501kARB 3FREE AGENT

Pre-arbitration (30)

PlayerAgeSvc2018201920202021202220232024
Chris Martin320.133$1.8MPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tony Barnette342.000$1.5MARB 1ARB 2ARB 3FREE AGENT
Ryan Rua282.129$870kARB 1ARB 2ARB 3FREE AGENT
Juan Centeno281.140$600kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Alex Claudio262.114$568kARB 1ARB 2ARB 3FREE AGENT
Nomar Mazara232.000$564kARB 1ARB 2ARB 3FREE AGENT
Delino DeShields252.116$562kARB 1ARB 2ARB 3FREE AGENT
Joey Gallo241.103$560kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Matt Bush321.143$556kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Carlos Tocci220.000$545kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Isiah Kiner-Falefa230.000$510kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
José Leclerc241.041$502kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Eddie Butler271.141$419kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Drew Robinson260.103$315kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Ricardo Rodríguez250.138$237kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Carlos Pérez272.021$234kARB 1ARB 2ARB 3FREE AGENT
Austin Bibens-Dirkx33$199k
Brandon Mann34$193k
Ariel Jurado220.000$193kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jeffrey Springs250.000$182kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Hanser Alberto252.023$176kARB 1ARB 2ARB 3FREE AGENT
Willie Calhoun230.020$158kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Yohander Méndez230.059$129kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Nick Gardewine240.083$117kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Connor Sadzeck262.047$91kARB 1ARB 2ARB 3FREE AGENT
Adrian Sampson260.107$79kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
CD Pelham230.000$79kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Zac Curtis250.097$47kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Renato Núñez240.048$38kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jose Trevino250.000$9kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Rangers 2018; 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.761, -10.5], [1986, 0.541, 6.06], [1987, 0.555, -3.68], [1988, 0.539, 0.55], [1989, 0.738, 6.82], [1990, 0.843, 3.23], [1991, 0.813, 3.36], [1992, 0.954, -0.6], [1993, 1.132, -0.48], [1994, 1.015, -10.92], [1995, 1.052, -4.66], [1996, 1.218, 9.85], [1997, 1.279, -4.79], [1998, 1.373, 7.48], [1999, 1.597, 4.83], [2000, 1.206, -15.32], [2001, 1.327, -9.37], [2002, 1.56, -11.06], [2003, 1.218, -7.47], [2004, 0.657, 4.08], [2005, 0.79, 3.13], [2006, 0.841, -0.45], [2007, 0.742, -6.38], [2008, 0.776, 1.96], [2009, 0.735, 3.33], [2010, 0.74, 10.61], [2011, 1.001, 18.64], [2012, 1.343, 7.71], [2013, 1.204, 4.11], [2014, 1.199, -15.5], [2015, 1.381, -5.41], [2016, 1.366, -8.94], [2017, 1.157, -14.69], [2018, 0.931, -12.88], [2019, 0.794, -8.18], [2021, 0.445, -6.87], [2022, 0.895, -6.34], [2023, 1.375, 10.82], [2024, 1.631, -10.98], [2025, 1.429, -4.26], [2026, 1.133, 3.02]], TEAMCOL = '#003278'; 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 = 2018; 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": ["RP", "SP", "IF", "IF", "OF", "OF", "IF", "RP", "RP", "C", "IF", "RP", "RP", "OF", "SP", "OF", "SP", "IF", "IF", "SP", "SP", "RP", "RP", "SP", "RP", "RP", "RP", "RP", "RP", "RP", "RP", "SP", "RP", "SP", "C", "RP", "SP", "RP", "RP", "IF"], "league": [5.36, 3.98, 3.48, 3.03, 2.66, 2.34, 2.06, 1.82, 1.65, 1.41, 1.3, 1.15, 1.0, 0.9, 0.82, 0.76, 0.67, 0.6, 0.52, 0.47, 0.41, 0.35, 0.3, 0.26, 0.22, 0.18, 0.15, 0.12, 0.09, 0.06, 0.04, 0.01, -0.01, -0.03, -0.05, -0.07, -0.09, -0.11, -0.13, -0.18], "names": ["Jos\u00e9 Leclerc", "Mike Minor", "Rougned Odor", "Jurickson Profar", "Joey Gallo", "Shin-Soo Choo", "Adrian Beltr\u00e9", "Keone Kela", "Alex Claudio", "Robinson Chirinos", "Elvis Andrus", "Jake Diekman", "Chris Martin", "Delino DeShields", "Cole Hamels", "Nomar Mazara", "Yovani Gallardo", "Isiah Kiner-Falefa", "Ronald Guzm\u00e1n", "Doug Fister", "Bartolo Colon", "Cory Gearrin", "Tony Barnette", "Ariel Jurado", "Jeffrey Springs", "Jesse Chavez", "Austin Bibens-Dirkx", "Matt Moore", "Ricardo Rodr\u00edguez", "CD Pelham", "Nick Gardewine", "Yohander M\u00e9ndez", "Connor Sadzeck", "Adrian Sampson", "Jose Trevino", "Brandon Mann", "Mart\u00edn P\u00e9rez", "Matt Bush", "Zac Curtis", "Hanser Alberto"], "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": [2.61, 2.59, 2.38, 2.2, 2.16, 1.93, 1.15, 1.13, 0.95, 0.87, 0.82, 0.74, 0.65, 0.62, 0.61, 0.55, 0.52, 0.46, 0.4, 0.39, 0.38, 0.35, 0.33, 0.31, 0.23, 0.23, 0.21, 0.19, 0.14, 0.12, 0.08, 0.05, -0.01, -0.01, -0.06, -0.08, -0.09, -0.1, -0.11, -0.17]}; 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:'TEX',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);});