The Trends feature provides visual analysis of your team’s performance across the fantasy basketball season. Using interactive Chart.js line graphs, you can track how your statistics evolve week by week.
Overview
Trends displays week-by-week data for your fantasy team in an interactive line chart. You can:
Select any stat category to visualize
See performance trends across the entire season
Identify hot and cold streaks
Compare current performance to historical averages
Trends data is fetched for all weeks up to the current week, allowing you to see your complete season progression.
How It Works
Initial Data Loading
When you activate the Trends tab, the system performs an optimized parallel data fetch:
fetchAllWeeklyData : async () => {
// 1. Fetch league settings and current week in one call
Utils . updateLoadingMessage ( 'Loading league settings & current week…' );
const settingsResult = await DataService . tryGetLeagueSettingsAndCurrentWeek ();
const numWeeksToFetch = STATE . currentWeek ;
// 2. Kick off all week requests in parallel
const weekFetchPromises = Array . from ({ length: numWeeksToFetch }, ( _ , i ) => {
const weekNum = i + 1 ;
return fetch ( `/api/scoreboard?week= ${ weekNum } ` )
. then ( response => response . ok ? response . json () : null )
. then ( jsonData => {
if ( jsonData ) {
const teams = DataService . extractTeams ( jsonData );
const myTeamData = teams . find ( team => team . isMine );
if ( myTeamData ) {
weeklyStatsData [ weekNum - 1 ] = { week: weekNum , stats: myTeamData . statMap };
}
}
});
});
await Promise . allSettled ( weekFetchPromises );
}
The parallel fetching strategy significantly reduces load times compared to sequential requests, especially for leagues deep into the season.
Progress Tracking
A visual progress indicator shows real-time loading status:
updateLoadingMessage : ( msg , done = null , total = null ) => {
if ( DOM . loadingText ) DOM . loadingText . textContent = msg ;
if ( done != null && total != null && DOM . loadingProgress ) {
DOM . loadingProgress . style . width = ` ${ Math . round (( done / total ) * 100 ) } %` ;
if ( DOM . loadingCurrentWeek )
DOM . loadingCurrentWeek . textContent = `Week ${ done } of ${ total } ` ;
}
}
Chart.js Visualizations
Chart Configuration
The chart is rendered using Chart.js with responsive settings:
DOM . chart = new Chart ( ctx , {
type: 'line' ,
data: {
labels: labels , // Week numbers
datasets: [{
label: statName ,
data: dataPoints , // Stat values
borderColor: '#4299e1' ,
backgroundColor: 'rgba(66,153,225,.2)' ,
pointBackgroundColor: '#4299e1' ,
pointBorderColor: '#fff' ,
borderWidth: 2 ,
pointRadius: 4 ,
pointHoverRadius: 6 ,
tension: 0.1 ,
spanGaps: true // Connect lines over null data
}]
},
options: {
responsive: true ,
maintainAspectRatio: false ,
scales: {
y: {
beginAtZero: false ,
ticks: {
precision: isPercentage ? 3 : 0
}
},
x: {
title: {
display: true ,
text: 'Week'
}
}
}
}
});
Color Scheme
The chart uses a consistent blue color palette:
generateChartColors : () => ({
borderColor: '#4299e1' ,
backgroundColor: 'rgba(66,153,225,.2)' ,
pointBackgroundColor: '#4299e1' ,
pointBorderColor: '#fff' ,
pointHoverBackgroundColor: '#fff' ,
pointHoverBorderColor: '#3182ce'
})
Interpreting Trend Lines
Understanding the Visualization
The line chart shows:
X-axis : Week numbers (1, 2, 3, …)
Y-axis : Stat values (automatically scaled)
Data points : Your team’s performance each week
Line : Connects weekly performances to show trends
Handling Missing Data
The chart gracefully handles weeks with no data:
STATE . weeklyStats . forEach (( weekData , index ) => {
if ( weekData ) {
labels . push ( index + 1 );
const statValue = Utils . formatStatValue ( STATE . selectedStat , weekData . stats [ STATE . selectedStat ]);
dataPoints . push ( statValue );
} else {
labels . push ( index + 1 );
dataPoints . push ( null ); // spanGaps: true connects over nulls
}
});
Missing weeks appear as gaps in the trend line, which are automatically bridged to show overall progression.
Stat Selector
Switch between different statistics using the dropdown:
initStatSelector : () => {
DOM . statSelector . innerHTML = '' ;
STATE . statCategories . forEach (([ id , name , dir ]) => {
const option = document . createElement ( 'option' );
option . value = id ;
option . textContent = name ;
DOM . statSelector . appendChild ( option );
});
STATE . selectedStat = STATE . statCategories [ 0 ][ 0 ];
DOM . statSelector . value = STATE . selectedStat ;
DOM . statSelector . addEventListener ( 'change' , () => {
STATE . selectedStat = DOM . statSelector . value ;
ChartRenderer . renderChart ();
});
}
Percentage stats receive special formatting:
formatStatValue : ( id , v ) => {
if ( v == null || v === '' ) return null ;
const num = parseFloat ( v );
if ( isNaN ( num )) return null ;
return CONFIG . PERCENTAGE_STATS . includes ( id )
? num . toFixed ( 3 ). replace ( / ^ 0 \. / , '.' ) // .450 instead of 0.450
: num ;
}
In the chart tooltip:
label : ( context ) => {
let value = context . parsed . y ;
if ( value === null ) return ` ${ statName } : N/A` ;
if ( isPercentage ) {
return ` ${ statName } : ${ parseFloat ( value ). toFixed ( 3 ). replace ( / ^ 0 \. / , '.' ) } ` ;
}
return ` ${ statName } : ${ Number . isInteger ( value ) ? value : parseFloat ( value ). toFixed ( 2 ) } ` ;
}
Hover over any data point to see detailed information:
plugins : {
tooltip : {
callbacks : {
title : ( tooltipItems ) => `Week ${ tooltipItems [ 0 ]. label } ` ,
label : ( context ) => {
let value = context . parsed . y ;
if ( value === null ) return ` ${ statName } : N/A` ;
if ( isPercentage ) {
return ` ${ statName } : ${ parseFloat ( value ). toFixed ( 3 ). replace ( / ^ 0 \. / , '.' ) } ` ;
}
return ` ${ statName } : ${ Number . isInteger ( value ) ? value : parseFloat ( value ). toFixed ( 2 ) } ` ;
}
}
}
}
Loading States
The feature includes several loading messages that cycle while fetching data:
loadingMessages : [
'Initializing trends system…' ,
'Getting league settings…' ,
'Connecting to Yahoo API…' ,
'Analyzing statistics…' ,
'Synchronizing data…' ,
'Reading performance metrics…' ,
'Preparing visualization…' ,
'Almost there!'
]
Messages cycle every 2 seconds:
cycleLoadingMessages : () => {
let idx = 0 ;
return setInterval (() => {
if ( ! DOM . loadingText ) return ;
if ( DOM . loadingText . textContent . includes ( 'Week ' )) return ; // Don't override progress
DOM . loadingText . textContent = STATE . loadingMessages [ idx ];
idx = ( idx + 1 ) % STATE . loadingMessages . length ;
}, 2000 );
}
Configuration Merging
Trends shares configuration with dashboard.js:
if ( window . CONFIG ) {
Object . assign ( CONFIG , window . CONFIG );
}
This ensures:
Consistent category definitions
Shared percentage stat identification
Unified stat metadata
If dashboard.js hasn’t loaded, trends.js uses its own fallback configuration to ensure functionality.
Responsive Chart
The chart automatically resizes based on container dimensions:
options : {
responsive : true ,
maintainAspectRatio : false ,
// ...
}
This ensures optimal viewing on all screen sizes.
Technical Details
State Management
const STATE = {
currentWeek: 0 ,
weeklyStats: [],
statCategories: [],
selectedStat: null ,
initialized: false ,
dataLoaded: false
};
extractTeams : data => {
const league = ( data . fantasy_content . league || []). find ( l => l . scoreboard );
if ( ! league ) return [];
const matchups = league . scoreboard [ '0' ]?. matchups || league . scoreboard . matchups || {};
const teams = [];
Object . values ( matchups ). forEach ( mw => {
const ts = mw . matchup ?. teams || mw . matchup ?.[ '0' ]?. teams || {};
Object . values ( ts ). forEach ( tw => {
const arr = tw . team ;
let name = '—' , isMine = false ;
// Check if this is the user's team
( arr [ 0 ] || []). forEach ( it => {
if ( Utils . yes ( it ?. is_current_login )) isMine = true ;
});
const statMap = {};
( arr [ 1 ]?. team_stats ?. stats || []). forEach ( s => {
statMap [ s . stat . stat_id ] = s . stat . value ;
});
teams . push ({ name , isMine , statMap });
});
});
return teams ;
}
Error Handling
Robust error handling ensures graceful degradation:
try {
STATE . weeklyStats = await DataService . fetchAllWeeklyData ();
if ( ! STATE . weeklyStats || STATE . weeklyStats . every ( s => s === null )) {
DOM . container . insertAdjacentHTML ( 'beforeend' ,
'<p class="error-message">Could not load trends data. Please try again.</p>'
);
STATE . dataLoaded = false ;
return ;
}
STATE . dataLoaded = true ;
ChartRenderer . renderChart ();
} catch ( err ) {
console . error ( 'Critical error in fetchAllWeeklyData:' , err );
DOM . container . innerHTML =
'<p class="error-message">An unexpected error occurred. Please try again.</p>' ;
}
Next Steps
Player Contributions See which players drive your trends
Category Strengths Analyze specific week performance