In The Game
  • Home
  • Blog
  • AFL
    • Overview

    • Player Stats
    • Player Ratings
    • Player Game Logs
    • Player Comparison

    • Team Stats
    • Team Game Logs
    • Team Ratings

    • Matches
    • Ladder
    • Definitions
  • Football
    • Overview

    • Player Stats
    • Player Ratings
    • Player Game Logs
    • Player Comparison

    • Team Stats
    • Team Game Logs
    • Team Ratings

    • Leagues
    • Matches
    • Definitions
  • About
Skip to content

Football Team Game Logs

Team-level game performances — find the best single-game team performances across European football

Football > Team Game Logs

Team-level game performances — each row is one team in one match. Sort by any stat column to find the best single-game team performances (e.g., most goals, most tackles). High = above average for that stat. Low = below average.

Show code
statsEsc = window.statsEsc
statsTable = window.statsTable
base = window.DATA_BASE_URL

leagueCodes = window.footballMaps.domesticLeagues
leagueNames = window.footballMaps.leagueNames

statDefs = window.footballStatDefs || {}

// Categories: exclude ratings and custom
catKeys = Object.keys(statDefs).filter(k => !statDefs[k].page && k !== "custom")
Show code
// ── Category toggle ──────────────────────────────────────────
viewof category = {
  const _key = "_teamGameLogCat_" + window.location.pathname.replace(/[^a-z0-9]/gi, "_")
  const _saved = window[_key] || "scoring"
  const _default = catKeys.includes(_saved) ? _saved : "scoring"
  const container = html`<div class="stats-category-toggle football"></div>`
  for (const key of catKeys) {
    const btn = document.createElement("button")
    btn.className = "stats-cat-btn" + (key === _default ? " active" : "")
    btn.textContent = statDefs[key].label
    btn.dataset.cat = key
    btn.addEventListener("click", () => {
      container.querySelectorAll(".stats-cat-btn").forEach(b => b.classList.remove("active"))
      btn.classList.add("active")
      container.value = key
      window[_key] = key
      container.dispatchEvent(new Event("input", { bubbles: true }))
    })
    container.appendChild(btn)
  }
  container.value = _default
  return container
}
Show code
viewof filters = {
  function makeSelect(options, defaultVal, label) {
    const wrap = document.createElement("div")
    wrap.className = "filter-select-wrap"
    const lbl = document.createElement("span")
    lbl.className = "filter-label"
    lbl.textContent = label
    const sel = document.createElement("select")
    sel.className = "filter-select"
    for (const opt of options) {
      const o = document.createElement("option")
      o.value = opt; o.textContent = opt
      if (opt === defaultVal) o.selected = true
      sel.appendChild(o)
    }
    wrap.appendChild(lbl); wrap.appendChild(sel)
    return { wrap, sel }
  }

  const container = document.createElement("div")
  container.className = "player-filter-bar"
  const row = document.createElement("div")
  row.className = "filter-row"

  const leagueOpts = ["All Leagues", ...leagueCodes.map(c => `${leagueNames[c] || c} (${c})`)]
  const league = makeSelect(leagueOpts, leagueOpts.find(l => l.includes("ENG")) || leagueOpts[0], "League")

  const season = makeSelect(["All Seasons"], "All Seasons", "Season")

  row.appendChild(league.wrap)
  row.appendChild(season.wrap)
  container.appendChild(row)

  container.value = { league: "ENG", season: "All Seasons" }

  function emit() { container.dispatchEvent(new Event("input", { bubbles: true })) }

  league.sel.addEventListener("change", () => {
    const raw = league.sel.value
    const code = raw === "All Leagues" ? "All Leagues" : raw.match(/\((\w+)\)$/)?.[1] || raw
    container.value = { ...container.value, league: code, season: "All Seasons" }
    emit()
  })
  season.sel.addEventListener("change", () => {
    container.value = { ...container.value, season: season.sel.value }
    emit()
  })

  container._updateSeasons = function (opts, defaultVal) {
    const oldVal = container.value.season
    while (season.sel.firstChild) season.sel.removeChild(season.sel.firstChild)
    for (const opt of opts) {
      const o = document.createElement("option")
      o.value = opt; o.textContent = opt
      if (opt === defaultVal) o.selected = true
      season.sel.appendChild(o)
    }
    if (oldVal !== defaultVal) {
      container.value = { ...container.value, season: defaultVal }
      emit()
    }
  }

  return container
}

leagueFilter = filters.league
seasonFilter = filters.season
Show code
matchStats = {
  const codes = leagueFilter === "All Leagues" ? leagueCodes : [leagueFilter]
  const results = []
  for (const code of codes) {
    try {
      const data = await window.fetchParquet(base + `football/match-stats-${code}.parquet`)
      if (data) results.push(...data)
    } catch (e) {
      console.warn(`[team-game-logs] match-stats-${code} load failed:`, e)
    }
  }
  return results.length > 0 ? results : null
}

// Load game-logs for Value tab
_gameLogs = {
  try { return await window.fetchParquet(base + "football/game-logs.parquet") } catch (e) { return null }
}

seasonOptions = {
  const src = matchStats || _gameLogs
  if (!src) return ["All Seasons"]
  const seasons = [...new Set(src.map(d => String(d.season)))].sort().reverse()
  return ["All Seasons", ...seasons]
}

// Update season dropdown
{
  const el = document.querySelector(".player-filter-bar")
  if (el && el._updateSeasons) {
    const defaultSeason = seasonOptions[1] || "All Seasons"
    el._updateSeasons(seasonOptions, defaultSeason)
  }
}
Show code
// ── Build team game log table data ──────────────────────────
tableData = {
  const catDef = statDefs[category]
  if (!catDef) return null

  const source = catDef.source
  let rawData
  if (source === "gameLogs") {
    rawData = _gameLogs
  } else {
    rawData = matchStats
  }
  if (!rawData) return null

  const effectiveSeason = seasonFilter === "All Seasons" ? null : seasonFilter

  let games = rawData

  // League filter (for game-logs which may span leagues)
  if (leagueFilter !== "All Leagues" && source === "gameLogs") {
    games = games.filter(d => d.league === leagueFilter)
  }

  // Season filter
  if (effectiveSeason) games = games.filter(d => String(d.season) === effectiveSeason)

  // Aggregate player rows into team-game rows
  const statCols = catDef.columns.filter(c => c !== "pass_pct")
  const teamGameMap = new Map()

  for (const row of games) {
    const team = row.team_name || ""
    if (!team) continue
    const date = row.match_date ? String(row.match_date).replace("Z", "").slice(0, 10) : ""
    const opp = row.opponent || ""
    const key = `${team}|||${date}|||${opp}`

    if (!teamGameMap.has(key)) {
      teamGameMap.set(key, { team, date, opponent: opp, season: row.season, vals: {} })
      for (const c of statCols) teamGameMap.get(key).vals[c] = 0
    }
    const entry = teamGameMap.get(key)
    for (const c of statCols) {
      entry.vals[c] = (entry.vals[c] || 0) + (Number(row[c]) || 0)
    }
  }

  // Build output rows
  return [...teamGameMap.values()].map(e => {
    const row = {
      team: e.team,
      date: e.date,
      opponent: e.opponent
    }
    for (const col of statCols) {
      const v = e.vals[col]
      row[col] = isNaN(v) ? null : +(v.toFixed(1))
    }
    // Recompute pass_pct from summed totals
    if (catDef.compute && row.passes > 0) {
      row.pass_pct = Math.round(100 * row.passes_accurate / row.passes)
    } else if (catDef.compute) {
      row.pass_pct = null
    }
    return row
  })
}
Show code
viewof search = tableData == null
  ? html``
  : Inputs.search(tableData, { placeholder: "Search teams..." })
Show code
// ── Render table ─────────────────────────────────────────────
{
  if (tableData == null || tableData.length === 0)
    return html`<p class="text-muted">No data available for this category and filter combination.</p>`

  const catDef = statDefs[category]
  const statCols = catDef.columns

  const columns = ["team", "date", "opponent", ...statCols]

  const header = {
    team: "Team", date: "Date", opponent: "Vs",
    ...catDef.header
  }

  const format = {}
  for (const col of statCols) {
    format[col] = v => v != null ? String(Math.round(Number(v))) : ""
  }

  const heatmap = {}
  for (const col of statCols) {
    if (catDef.heatmap?.[col]) heatmap[col] = catDef.heatmap[col]
  }

  const render = {}
  const renderTeamCell = window.footballMaps?.renderTeamCell
  if (renderTeamCell) {
    render.team = (val) => renderTeamCell(val)
    render.opponent = (val) => renderTeamCell(val)
  }

  const groups = [
    { label: "", span: 3 },
    { label: catDef.label, span: statCols.length }
  ]

  const sortCol = catDef.sortCol || statCols[0]

  return statsTable(search, {
    columns, header, groups, format, heatmap, render,
    sort: sortCol, reverse: true,
    pageSize: 25
  })
}
 

Pete Owen · Sydney · © 2026 · Source

Privacy | Disclaimer