Overview
The Fantasy Basketball Analytics dashboard provides multiple views for analyzing your team’s performance. The interface uses a tab-based navigation system with responsive design for desktop and mobile devices.
The header displays your team identity and league information:
< header class = "header" >
< div class = "header-content" >
< div class = "team-logo-container" >
< img id = "teamLogo" class = "team-logo" src = "" alt = "Team Logo" >
</ div >
< div class = "team-info" >
< h1 class = "team-title" > {{ team_name }} </ h1 >
< span class = "league-id" > League ID: {{ league_key }} </ span >
</ div >
</ div >
</ header >
Team Logo Loading
The team logo is fetched asynchronously via the /api/team_logo endpoint:
fetch ( '/api/team_logo' )
. then ( response => response . json ())
. then ( data => {
if ( data . logo_url ) {
document . getElementById ( 'teamLogo' ). src = data . logo_url ;
}
});
The logo comes from Yahoo’s Fantasy API and reflects your team’s custom logo or the default Yahoo placeholder.
Main Navigation Tabs
The dashboard has five main tabs:
< nav class = "nav-tabs" >
< button class = "nav-tab active" data-target = "tab-cat" > Category Strengths by Week </ button >
< button class = "nav-tab" data-target = "tab-compare" > Compare Teams </ button >
< button class = "nav-tab" data-target = "tab-trends" > Trends </ button >
< button class = "nav-tab" data-target = "tab-draft" > Draft </ button >
< button class = "nav-tab" data-target = "tab-trade" > Trade Analyzer </ button >
</ nav >
Tab Switching Logic
document . querySelectorAll ( '.nav-tab' ). forEach ( tab => {
tab . addEventListener ( 'click' , () => {
// Remove active class from all tabs and panes
document . querySelectorAll ( '.nav-tab' ). forEach ( t => t . classList . remove ( 'active' ));
document . querySelectorAll ( '.tab-pane' ). forEach ( p => p . classList . remove ( 'active' ));
// Activate clicked tab and its pane
tab . classList . add ( 'active' );
document . getElementById ( tab . dataset . target ). classList . add ( 'active' );
});
});
Tab 1: Category Strengths by Week
Analyze your team’s performance against every other team for a specific week.
Controls
< div class = "filters" >
< div class = "week-selector" >
< span class = "week-label" > Week: </ span >
< select id = "weekSel" ></ select >
</ div >
< div class = "team-selector" >
< span class = "team-label" > Viewing as: </ span >
< select id = "weeklyTeamSel" class = "team-select" ></ select >
</ div >
< label class = "checkbox-container" >
< input type = "checkbox" id = "showRanks" checked >
Show team ranks per category
</ label >
</ div >
Week Selection
Weeks are populated dynamically based on league settings:
Renderer . initWeekSelector : () => {
const totalWeeks = Math . max ( CONFIG . MAX_WEEKS , STATE . weekly . currentWeek || 1 );
for ( let w = 1 ; w <= totalWeeks ; w ++ ) {
weekSel . insertAdjacentHTML ( 'beforeend' , `<option value=" ${ w } "> ${ w } </option>` );
}
}
The current week is automatically selected based on Yahoo’s league data. You can navigate to any week to analyze historical matchups.
Team Perspective
You can view matchups from any team’s perspective:
DOM . weekly . teamSel ?. addEventListener ( 'change' , () => {
const selectedIndex = parseInt ( DOM . weekly . teamSel . value );
STATE . weekly . selectedTeamIndex = selectedIndex ;
Renderer . renderWeekTable ( STATE . weekly . loadedTeams , selectedIndex );
Renderer . renderWeekCards ( STATE . weekly . loadedTeams , selectedIndex );
});
Tab 2: Compare Teams
Compare season totals or per-week averages across all teams.
View Toggle
< div class = "week-selector" >
< span class = "week-label" > View: </ span >
< select id = "viewSel" >
< option value = "tot" selected > Totals </ option >
< option value = "avg" > Averages </ option >
</ select >
</ div >
Data Processing
processSeasonData : ( payload , mode ) => {
return teams . map ( t => {
const sm = {};
CONFIG . COLS . forEach (([ id , ,]) => {
const numVal = parseFloat ( t . statMap [ id ]);
if ( mode === 'avg' ) {
if ( CONFIG . PERCENTAGE_STATS . includes ( id )) {
sm [ id ] = formatStatValue ( id , numVal );
} else {
sm [ id ] = ( numVal / currentWeek ). toFixed ( 0 );
}
} else {
sm [ id ] = formatStatValue ( id , numVal );
}
});
return { ... t , statMap: sm };
});
}
Understanding Totals vs Averages
Totals : Cumulative stats for the entire season to date
Averages : Per-week averages, calculated by dividing totals by the current week
Percentage stats : Always shown as actual percentages, never averaged
Use totals to see overall production
Use averages to normalize for teams that played different numbers of weeks
Tab 3: Trends
Visualize how your team’s performance changes over time.
Initial Load
< button id = "loadTrendsBtn" class = "btn btn-primary load-trends-btn" >
Load Trends Data
</ button >
Loading Progress
Utils . 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 } ` ;
}
}
Trends data requires fetching scoreboard data for every week, which can take 10-20 seconds. The progress bar shows real-time loading status.
Chart Display
After loading, you can:
Select any statistical category to chart
View week-by-week trends
Identify performance patterns
Tab 4: Draft (Keepers)
View keeper designations for all teams.
Season Selection
< div class = "filter-group" >
< span class = "filter-label" > Season </ span >
< select id = "keepersSeasonSelect" aria-label = "Season" >
< option value = "" > Loading... </ option >
</ select >
</ div >
Team Filter
< div class = "filter-group" >
< span class = "filter-label" > Team </ span >
< select id = "keepersTeamSelect" aria-label = "Team filter" >
< option value = "ALL" selected > All Teams </ option >
</ select >
</ div >
Export Functionality
< button id = "keepersExportBtn" class = "keepers-export" type = "button" disabled >
Export CSV
</ button >
Tab 5: Trade Analyzer
Evaluate potential trades by comparing player statistics.
Two-Column Interface
< div class = "trade-sides-container" >
< div class = "trade-column" >
< div class = "trade-column-title" > TRADING AWAY </ div >
< input type = "text" class = "player-search" id = "tradingPlayerSearch" placeholder = "Player Name" >
< div id = "tradingPlayersContainer" class = "players-container" ></ div >
</ div >
< div class = "trade-column" >
< div class = "trade-column-title" > ACQUIRING </ div >
< input type = "text" class = "player-search" id = "acquiringPlayerSearch" placeholder = "Player Name" >
< div id = "acquiringPlayersContainer" class = "players-container" ></ div >
</ div >
</ div >
Player Search
Type to search NBA players with autocomplete:
fetchNbaPlayers : async () => {
const response = await fetch ( '/api/nba_players' );
STATE . allNbaPlayers = await response . json ();
}
< button id = "evaluateTradeBtn" class = "evaluate-button" >
Evaluate Trade
</ button >
The trade analyzer shows how each trade would impact your category rankings using current season data and real-time NBA stats.
Responsive Design
Desktop View
On desktop (>768px width), data is displayed in tables:
Utils . initResponsiveView : () => {
let currentBreakpointIsMobile = window . innerWidth <= 768 ;
const setInitialViews = () => {
Utils . switchView ( currentBreakpointIsMobile ? 'cards' : 'table' , DOM . weekly );
Utils . switchView ( currentBreakpointIsMobile ? 'cards' : 'table' , DOM . compare );
};
}
Mobile View
On mobile (≤768px width), data switches to card format:
< div class = "view-toggle" >
< button id = "cardsView" class = "active" > Cards </ button >
< button id = "tableView" > Table </ button >
</ div >
Table View (Desktop):
Compact, information-dense layout
Easy to scan multiple teams at once
Sortable columns
Fixed headers for scrolling
Cards View (Mobile):
One team per card
Touch-friendly interface
Larger text for readability
Vertical scrolling
Navigation Bar
Global navigation is available at the top:
< nav class = "navbar" >
< a href = "{{ url_for('dashboard') }}" class = "navbar-brand" > Ibby's Fantasy App </ a >
< div class = "navbar-links" >
< a href = "{{ url_for('dashboard') }}" > Dashboard </ a >
< a href = "{{ url_for('about') }}" > About </ a >
< a href = "{{ url_for('ironman_rankings') }}" > Ironman Rankings </ a >
< a href = "https://github.com/AlexIbby/fantasyBasketball" > GitHub </ a >
< a href = "{{ url_for('logout') }}" > Logout </ a >
</ div >
</ nav >
< button class = "hamburger" >
< span class = "hamburger-line" ></ span >
< span class = "hamburger-line" ></ span >
< span class = "hamburger-line" ></ span >
</ button >
Lazy Loading
Tabs load data only when first accessed:
document . querySelector ( '[data-target="tab-compare"]' )?. addEventListener ( 'click' , () => {
if ( ! STATE . compare . seasonPayload ) {
API . loadSeasonStats (); // Only load if not already cached
}
});
Data Caching
Once loaded, data is stored in state:
const STATE = {
weekly: {
loadedTeams: [],
selectedTeamIndex: 0 ,
userTeamIndex: 0
},
compare: {
seasonPayload: null ,
seasonMode: 'tot' ,
selectedTeamIndex: 0
}
};
Next Steps
Interpreting Analytics Learn how to read and understand the analytics
API Reference Explore the backend API endpoints