Skip to main content

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.

Dashboard Header

The header displays your team identity and league information:
dashboard.html
<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:
team_logo.js
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.
The dashboard has five main tabs:
dashboard.html
<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

dashboard.js
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

dashboard.html
<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:
dashboard.js
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:
dashboard.js
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

dashboard.html
<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

dashboard.js
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 };
  });
}
  • 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
Visualize how your team’s performance changes over time.

Initial Load

dashboard.html
<button id="loadTrendsBtn" class="btn btn-primary load-trends-btn">
  Load Trends Data
</button>

Loading Progress

trends.js
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

dashboard.html
<div class="filter-group">
  <span class="filter-label">Season</span>
  <select id="keepersSeasonSelect" aria-label="Season">
    <option value="">Loading...</option>
  </select>
</div>

Team Filter

dashboard.html
<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

dashboard.html
<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

dashboard.html
<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>
Type to search NBA players with autocomplete:
trade_analyzer.js
fetchNbaPlayers: async () => {
  const response = await fetch('/api/nba_players');
  STATE.allNbaPlayers = await response.json();
}

Evaluation Button

dashboard.html
<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:
dashboard.js
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:
dashboard.html
<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
Global navigation is available at the top:
dashboard.html
<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>

Mobile Hamburger Menu

dashboard.html
<button class="hamburger">
  <span class="hamburger-line"></span>
  <span class="hamburger-line"></span>
  <span class="hamburger-line"></span>
</button>

Performance Optimization

Lazy Loading

Tabs load data only when first accessed:
dashboard.js
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:
dashboard.js
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

Build docs developers (and LLMs) love