Skip to contents
On this page

Introduction

This article demonstrates how to create interactive leaderboard dashboards using sdvplotR, gt tables, and Quarto. These patterns are perfect for tracking standings, player stats, and team performance in real-time.

Setup

library(sdvplotR)
library(ggplot2)
library(dplyr)
library(gt)

# Get valid team abbreviations
nfl_teams <- sdv_teams("nfl")$abbr
nba_teams <- sdv_teams("nba")$abbr
cfb_teams <- sdv_teams("cfb")$abbr

Basic Leaderboard with Logos

Create a simple leaderboard table with team logos:

# Sample standings data
standings <- data.frame(
  team = c("KC", "BUF", "SF", "PHI", "DAL", "MIA", "CIN", "BAL"),
  wins = c(9, 8, 8, 7, 7, 6, 6, 6),
  losses = c(2, 3, 3, 4, 4, 5, 5, 5),
  ties = c(0, 0, 0, 0, 0, 0, 0, 0),
  pct = c(0.818, 0.727, 0.727, 0.636, 0.636, 0.545, 0.545, 0.545)
) |>
  mutate(
    logo = team,
    rank = row_number()
  ) |>
  select(rank, logo, team, wins, losses, ties, pct)

standings |>
  gt() |>
  gt_sdv_logos(columns = "logo", league = "nfl", height = 35) |>
  fmt_number(columns = "pct", decimals = 3) |>
  cols_label(
    rank = "#",
    logo = "Team",
    team = "Abbrev",
    wins = "W",
    losses = "L",
    ties = "T",
    pct = "Win %"
  ) |>
  tab_header(
    title = "NFL Standings",
    subtitle = "Example data"
  ) |>
  tab_footnote(
    footnote = "Example data | Viz: sdvplotR",
    locations = cells_title(groups = "title")
  )
NFL Standings1
Example data
# Abbrev W L T Win %
1 Kansas City Chiefs KC 9 2 0 0.818
2 Buffalo Bills BUF 8 3 0 0.727
3 San Francisco 49ers SF 8 3 0 0.727
4 Philadelphia Eagles PHI 7 4 0 0.636
5 Dallas Cowboys DAL 7 4 0 0.636
6 Miami Dolphins MIA 6 5 0 0.545
7 Cincinnati Bengals CIN 6 5 0 0.545
8 Baltimore Ravens BAL 6 5 0 0.545
1 Example data | Viz: sdvplotR

Advanced Leaderboard with Team Colors

Create a leaderboard with team-colored rows:

# Add team colors to standings
standings_colored <- standings |>
  mutate(
    primary_color = sdv_team_colors("nfl", team, which = "primary"),
    secondary_color = sdv_team_colors("nfl", team, which = "secondary")
  )

standings_colored |>
  gt() |>
  gt_sdv_logos(columns = "logo", league = "nfl", height = 35) |>
  fmt_number(columns = "pct", decimals = 3) |>
  cols_label(
    rank = "#",
    logo = "Team",
    team = "Abbrev",
    wins = "W",
    losses = "L",
    ties = "T",
    pct = "Win %"
  ) |>
  tab_header(
    title = "NFL Standings",
    subtitle = "Example data"
  ) |>
  data_color(
    columns = "pct",
    palette = c("lightblue", "darkblue"),
    # pick black or white text by WCAG contrast (gt's default, APCA, put white text on
    # mid-blue cells at 3.1:1)
    contrast_algo = "wcag"
  )
NFL Standings
Example data
# Abbrev W L T Win % primary_color secondary_color
1 Kansas City Chiefs KC 9 2 0 0.818 #e31837 #ffb612
2 Buffalo Bills BUF 8 3 0 0.727 #00338d #c60c30
3 San Francisco 49ers SF 8 3 0 0.727 #aa0000 #b3995d
4 Philadelphia Eagles PHI 7 4 0 0.636 #004c54 #a5acaf
5 Dallas Cowboys DAL 7 4 0 0.636 #002244 #b0b7bc
6 Miami Dolphins MIA 6 5 0 0.545 #008e97 #f58220
7 Cincinnati Bengals CIN 6 5 0 0.545 #fb4f14 #000000
8 Baltimore Ravens BAL 6 5 0 0.545 #241773 #9e7c0c

Multi-Sport Leaderboard

Create leaderboards for multiple sports in one table:

# Sample multi-sport data
multi_sport_standings <- data.frame(
  league = c(rep("NFL", 4), rep("NBA", 4), rep("MLB", 4)),
  team = c("KC", "BUF", "SF", "PHI",
           "BOS", "DEN", "MIL", "PHX",
           "LAD", "ATL", "HOU", "BAL"),
  wins = c(9, 8, 8, 7, 25, 23, 22, 20, 95, 90, 88, 85),
  losses = c(2, 3, 3, 4, 8, 10, 11, 13, 67, 72, 74, 77)
) |>
  mutate(
    pct = wins / (wins + losses),
    rank = row_number()
  ) |>
  select(league, rank, team, wins, losses, pct)

multi_sport_standings |>
  gt() |>
  gt_sdv_logos(columns = "team", league = "nfl", height = 30, locations = cells_body(columns = team, rows = league == "NFL")) |>
  gt_sdv_logos(columns = "team", league = "nba", height = 30, locations = cells_body(columns = team, rows = league == "NBA")) |>
  gt_sdv_logos(columns = "team", league = "mlb", height = 30, locations = cells_body(columns = team, rows = league == "MLB")) |>
  fmt_number(columns = "pct", decimals = 3) |>
  cols_label(
    league = "Sport",
    rank = "#",
    team = "Team",
    wins = "W",
    losses = "L",
    pct = "Win %"
  ) |>
  tab_header(
    title = "Multi-Sport Leaderboard",
    subtitle = "Top Teams Across Sports"
  )
Multi-Sport Leaderboard
Top Teams Across Sports
Sport # Team W L Win %
NFL 1 Kansas City Chiefs 9 2 0.818
NFL 2 Buffalo Bills 8 3 0.727
NFL 3 San Francisco 49ers 8 3 0.727
NFL 4 Philadelphia Eagles 7 4 0.636
NBA 5 Boston Celtics 25 8 0.758
NBA 6 Denver Nuggets 23 10 0.697
NBA 7 Milwaukee Bucks 22 11 0.667
NBA 8 Phoenix Suns 20 13 0.606
MLB 9 Los Angeles Dodgers 95 67 0.586
MLB 10 Atlanta Braves 90 72 0.556
MLB 11 Houston Astros 88 74 0.543
MLB 12 Baltimore Orioles 85 77 0.525

Player Leaderboard with Headshots

Create a player stats table with headshots:

# Sample player data
player_stats <- data.frame(
  player_id = c("00-0033873", "00-0026498", "00-0035228", "00-0033869"),
  player_name = c("P. Mahomes", "M. Stafford", "K. Murray", "J. Allen"),
  team = c("KC", "LAR", "ARI", "BUF"),
  pass_yds = c(4200, 3800, 3500, 4100),
  pass_td = c(32, 28, 25, 30),
  pass_int = c(8, 12, 10, 9)
) |>
  mutate(
    rank = row_number()
  ) |>
  select(rank, player_id, player_name, team, pass_yds, pass_td, pass_int)

player_stats |>
  gt() |>
  gt_sdv_headshots(columns = "player_id", league = "nfl", id_system = "gsis", height = 40) |>
  fmt_number(columns = c("pass_yds", "pass_td", "pass_int")) |>
  cols_label(
    rank = "#",
    player_id = "Player",
    player_name = "Name",
    team = "Team",
    pass_yds = "Yards",
    pass_td = "TD",
    pass_int = "INT"
  ) |>
  tab_header(
    title = "NFL Quarterback Leaderboard",
    subtitle = "Top Passers by Yards"
  )
NFL Quarterback Leaderboard
Top Passers by Yards
# Player Name Team Yards TD INT
1 Player 00-0033873 headshot P. Mahomes KC 4,200.00 32.00 8.00
2 Player 00-0026498 headshot M. Stafford LAR 3,800.00 28.00 12.00
3 Player 00-0035228 headshot K. Murray ARI 3,500.00 25.00 10.00
4 Player 00-0033869 headshot J. Allen BUF 4,100.00 30.00 9.00

Quartile-Based Leaderboard

Create a leaderboard with quartile indicators:

# Add quartile information
standings_quartile <- standings |>
  mutate(
    quartile = case_when(
      pct >= 0.75 ~ "Q1 (Elite)",
      pct >= 0.50 ~ "Q2 (Good)",
      pct >= 0.25 ~ "Q3 (Average)",
      TRUE ~ "Q4 (Struggling)"
    )
  )

standings_quartile |>
  gt() |>
  gt_sdv_logos(columns = "logo", league = "nfl", height = 35) |>
  fmt_number(columns = "pct", decimals = 3) |>
  cols_label(
    rank = "#",
    logo = "Team",
    team = "Abbrev",
    wins = "W",
    losses = "L",
    ties = "T",
    pct = "Win %",
    quartile = "Quartile"
  ) |>
  tab_header(
    title = "NFL Standings by Quartile",
    subtitle = "Example data"
  ) |>
  tab_row_group(
    label = "Elite Teams",
    rows = pct >= 0.75
  ) |>
  tab_row_group(
    label = "Playoff Teams",
    rows = pct >= 0.50 & pct < 0.75
  )
NFL Standings by Quartile
Example data
# Abbrev W L T Win % Quartile
Playoff Teams
2 Buffalo Bills BUF 8 3 0 0.727 Q2 (Good)
3 San Francisco 49ers SF 8 3 0 0.727 Q2 (Good)
4 Philadelphia Eagles PHI 7 4 0 0.636 Q2 (Good)
5 Dallas Cowboys DAL 7 4 0 0.636 Q2 (Good)
6 Miami Dolphins MIA 6 5 0 0.545 Q2 (Good)
7 Cincinnati Bengals CIN 6 5 0 0.545 Q2 (Good)
8 Baltimore Ravens BAL 6 5 0 0.545 Q2 (Good)
Elite Teams
1 Kansas City Chiefs KC 9 2 0 0.818 Q1 (Elite)

Quarto Dashboard Integration

Create a Quarto dashboard with sdvplotR visualizations:

# In a Quarto document (.qmd), use this structure:

# ---
# title: "NFL Dashboard"
# format: dashboard
# ---

# ## Standings

# ```{r}
# standings |>
#   gt() |>
#   gt_sdv_logos(columns = "logo", league = "nfl")
# ```

# ## Team Performance

# ```{r}
# ggplot(standings, aes(x = reorder(team, pct), y = pct)) +
#   geom_col(aes(fill = team), width = 0.7) +
#   scale_fill_sdv(league = "nfl") +
#   theme_minimal()
# ```

Real-Time Leaderboard Updates

Create a leaderboard that updates automatically:

# Function to fetch and display standings
update_standings <- function(league = "nfl", week = NULL) {
  # In a real implementation, this would fetch live data
  # For demonstration, we'll use sample data

  message(paste("Updating", toupper(league), "standings..."))

  # Sample standings (replace with real data fetch)
  standings <- data.frame(
    team = sample(sdv_teams(league)$abbr, 10),
    wins = sample(0:16, 10),
    losses = sample(0:16, 10)
  ) |>
    mutate(
      pct = wins / (wins + losses),
      logo = team,
      rank = row_number()
    ) |>
    arrange(desc(pct)) |>
    head(10)

  standings |>
    gt() |>
    gt_sdv_logos(columns = "logo", league = league, height = 30) |>
    fmt_number(columns = "pct", decimals = 3)
}

# Update standings
update_standings("nfl")
team wins losses pct rank
MIN 16 0 1.000 Minnesota Vikings 4
IND 7 2 0.778 Indianapolis Colts 6
ARI 15 6 0.714 Arizona Cardinals 7
DAL 8 5 0.615 Dallas Cowboys 1
CHI 11 9 0.550 Chicago Bears 9
BAL 14 13 0.519 Baltimore Ravens 8
KC 5 7 0.417 Kansas City Chiefs 2
TB 2 3 0.400 Tampa Bay Buccaneers 10
LAC 3 16 0.158 Los Angeles Chargers 3
NE 0 11 0.000 New England Patriots 5

Conditional Formatting

Apply conditional formatting based on performance:

# Add performance indicators
standings_conditional <- standings |>
  mutate(
    performance = case_when(
      pct >= 0.75 ~ "πŸ”₯ Hot",
      pct >= 0.50 ~ "βœ… Good",
      pct >= 0.25 ~ "⚠️ Average",
      TRUE ~ "❌ Struggling"
    )
  )

standings_conditional |>
  gt() |>
  gt_sdv_logos(columns = "logo", league = "nfl", height = 35) |>
  fmt_number(columns = "pct", decimals = 3) |>
  cols_label(
    rank = "#",
    logo = "Team",
    team = "Abbrev",
    wins = "W",
    losses = "L",
    ties = "T",
    pct = "Win %",
    performance = "Status"
  ) |>
  tab_header(
    title = "NFL Standings with Status",
    subtitle = "Example data"
  )
NFL Standings with Status
Example data
# Abbrev W L T Win % Status
1 Kansas City Chiefs KC 9 2 0 0.818 πŸ”₯ Hot
2 Buffalo Bills BUF 8 3 0 0.727 βœ… Good
3 San Francisco 49ers SF 8 3 0 0.727 βœ… Good
4 Philadelphia Eagles PHI 7 4 0 0.636 βœ… Good
5 Dallas Cowboys DAL 7 4 0 0.636 βœ… Good
6 Miami Dolphins MIA 6 5 0 0.545 βœ… Good
7 Cincinnati Bengals CIN 6 5 0 0.545 βœ… Good
8 Baltimore Ravens BAL 6 5 0 0.545 βœ… Good

Integrating with oddsapiR

Combine standings with betting odds:

# Sample odds data (replace with real oddsapiR data)
odds_data <- data.frame(
  team = c("KC", "BUF", "SF", "PHI"),
  spread = c(-7.5, -3.5, -6.5, -10.5),
  moneyline = c(-350, -180, -280, -550),
  total = c(48.5, 51.5, 47.5, 45.5)
)

# Merge with standings
standings_with_odds <- standings |>
  filter(team %in% odds_data$team) |>
  left_join(odds_data, by = "team") |>
  mutate(logo = team)

standings_with_odds |>
  gt() |>
  gt_sdv_logos(columns = "logo", league = "nfl", height = 35) |>
  fmt_number(columns = c("pct", "spread", "total"), decimals = 1) |>
  fmt_number(columns = "moneyline", decimals = 0) |>
  cols_label(
    rank = "#",
    logo = "Team",
    team = "Abbrev",
    wins = "W",
    losses = "L",
    pct = "Win %",
    spread = "Spread",
    moneyline = "ML",
    total = "O/U"
  ) |>
  tab_header(
    title = "NFL Standings with Betting Odds",
    subtitle = "Combining Performance and Odds"
  )
NFL Standings with Betting Odds
Combining Performance and Odds
# Abbrev W L ties Win % Spread ML O/U
1 Kansas City Chiefs KC 9 2 0 0.8 βˆ’7.5 βˆ’350 48.5
2 Buffalo Bills BUF 8 3 0 0.7 βˆ’3.5 βˆ’180 51.5
3 San Francisco 49ers SF 8 3 0 0.7 βˆ’6.5 βˆ’280 47.5
4 Philadelphia Eagles PHI 7 4 0 0.6 βˆ’10.5 βˆ’550 45.5

Best Practices for Dashboards

  1. Keep it Simple: Focus on key metrics that matter to your audience

  2. Use Consistent Branding: Apply the same colors, fonts, and logos across all visualizations

  3. Make it Interactive: Use Quarto dashboards for interactive exploration

  4. Update Regularly: Set up automated updates to keep data fresh

  5. Optimize for Mobile: Ensure dashboards work on mobile devices

  6. Add Context: Include captions, footnotes, and data sources

Citation

To cite the sdvplotR R package in publications, use:

BibTeX Citation

@misc{gilani_carl_lee_kay_weatherman_sdvplotR,
  author = {Gilani, Saiem and Carl, Sebastian and Lee, Jared and Kay, Camden and Weatherman, Andrew},
  title = {sdvplotR: The SportsDataverse's R Package for Multi-League Sports Plotting.},
  url = {https://sdvplotR.sportsdataverse.org},
  year = {2026}
}