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

Cricket Player Profile

Bouncer player profile with batting and bowling ratings across formats
Show code
statsEsc = window.statsEsc
posBadge = window.posBadge
fetchParquet = window.fetchParquet
base_url = window.DATA_BASE_URL
Show code
t20_batting = fetchParquet(base_url + "cricket/t20-batting.parquet")
t20_bowling = fetchParquet(base_url + "cricket/t20-bowling.parquet")
odi_batting = fetchParquet(base_url + "cricket/odi-batting.parquet")
odi_bowling = fetchParquet(base_url + "cricket/odi-bowling.parquet")
test_batting = fetchParquet(base_url + "cricket/test-batting.parquet")
test_bowling = fetchParquet(base_url + "cricket/test-bowling.parquet")
Show code
playerName = {
  const raw = window._getHashParam("name")
  return raw ? raw.replace(/\+/g, " ") : null
}

// Find player across all formats (search full_name first, fall back to player)
playerData = {
  if (!playerName) return null
  const find = (data) => data ? (data.find(d => d.full_name === playerName) || data.find(d => d.player === playerName)) : null
  return {
    t20_bat: find(t20_batting),
    t20_bowl: find(t20_bowling),
    odi_bat: find(odi_batting),
    odi_bowl: find(odi_bowling),
    test_bat: find(test_batting),
    test_bowl: find(test_bowling)
  }
}

// Get first available record for bio info
playerBio = {
  if (!playerData) return null
  const first = playerData.t20_bat || playerData.t20_bowl || playerData.odi_bat || playerData.odi_bowl || playerData.test_bat || playerData.test_bowl
  if (!first) return null
  let age = null
  if (first.dob) {
    const ms = Date.now() - new Date(first.dob).getTime()
    age = +(ms / 31557600000).toFixed(1)
  }
  return {
    name: first.player,
    country: first.country || "",
    full_name: first.full_name || "",
    batting_style: first.batting_style || "",
    bowling_style: first.bowling_style || "",
    age
  }
}
Show code
batStyleColors = window.cricketStyleMaps.batStyleColors
bowlStyleColors = window.cricketStyleMaps.bowlStyleColors
Show code
// Breadcrumb + player header
{
  if (!playerName) {
    return html`<div class="breadcrumb"><a href="index.html">Cricket</a> > <a href="player-ratings.html">Player Ratings</a></div>
    <p class="text-muted">No player selected. Go to <a href="player-ratings.html">Player Ratings</a> and click a player name.</p>`
  }

  const allNull = [t20_batting, t20_bowling, odi_batting, odi_bowling, test_batting, test_bowling].every(d => d === null)
  if (allNull) {
    return html`<div class="breadcrumb"><a href="index.html">Cricket</a> > <a href="player-ratings.html">Player Ratings</a></div>
    <p class="text-muted">Player data could not be loaded. Try refreshing the page.</p>`
  }

  if (!playerBio) {
    return html`<div class="breadcrumb"><a href="index.html">Cricket</a> > <a href="player-ratings.html">Player Ratings</a></div>
    <p class="text-muted">Player "${statsEsc(playerName)}" not found in any format. <a href="player-ratings.html">Browse all players</a>.</p>`
  }

  const name = playerBio.full_name || playerBio.name || playerName
  const initials = name.split(" ").map(w => w[0]).join("").substring(0, 2).toUpperCase()
  const teamColor = "#f59e0b" // cricket amber accent

  // Style badges as inline spans
  function makeBadge(val, colorMap) {
    const p = colorMap[val] || { a: String(val || "").substring(0, 3), c: "#9ca3af" }
    return html`<span class="pos-badge" style="background:${p.c}18;color:${p.c};border:1px solid ${p.c}35">${p.a}</span>`
  }

  const badges = []
  const normBat = window.cricketStyleMaps.normalizeBatStyle
  const normBowl = window.cricketStyleMaps.normalizeBowlStyle
  if (playerBio.batting_style) badges.push(makeBadge(normBat(playerBio.batting_style), batStyleColors))
  if (playerBio.bowling_style) badges.push(makeBadge(normBowl(playerBio.bowling_style), bowlStyleColors))

  // Info line
  const parts = []
  if (playerBio.country) parts.push(playerBio.country)
  if (playerBio.age) parts.push(`Age ${playerBio.age}`)
  const infoLine = parts.join(" · ")

  return html`<div class="breadcrumb"><a href="index.html">Cricket</a> > <a href="player-ratings.html">Player Ratings</a> > ${name}</div>
  <div class="player-header">
    <div class="player-avatar" style="background:${teamColor}18;color:${teamColor};border:2px solid ${teamColor}">${initials}</div>
    <div class="player-info">
      <div class="player-name">${name}</div>
      <div class="player-meta">${badges}</div>
      ${infoLine ? html`<div class="player-bio">${infoLine}</div>` : html``}
    </div>
  </div>`
}
Show code
radarAxes = ["Bat Score", "Bat Survival", "Bowl Economy", "Bowl Strike Rate"]

// Percentile helper: % of values <= val
pctile = (vals, val) => {
  if (!vals || vals.length === 0) return null
  const sorted = vals.filter(v => v != null).sort((a, b) => a - b)
  if (sorted.length === 0) return null
  const rank = sorted.filter(v => v <= val).length
  return Math.round((rank / sorted.length) * 100)
}

// For bowling metrics, lower is better — invert the percentile
pctileInv = (vals, val) => {
  const p = pctile(vals, val)
  return p != null ? 100 - p : null
}

// Build radar data per format
radarFormats = {
  if (!playerData || !playerBio) return []

  const formatDefs = [
    { key: "t20", label: "T20", color: "#22d3ee", bat: t20_batting, bowl: t20_bowling, pBat: playerData.t20_bat, pBowl: playerData.t20_bowl },
    { key: "odi", label: "ODI", color: "#a78bfa", bat: odi_batting, bowl: odi_bowling, pBat: playerData.odi_bat, pBowl: playerData.odi_bowl },
    { key: "test", label: "Test", color: "#fb923c", bat: test_batting, bowl: test_bowling, pBat: playerData.test_bat, pBowl: playerData.test_bowl }
  ]

  return formatDefs.filter(f => f.pBat || f.pBowl).map(f => {
    const batScoreP = f.pBat ? pctile(f.bat?.map(d => d.scoring_index), f.pBat.scoring_index) : null
    const batSurvP = f.pBat ? pctile(f.bat?.map(d => d.survival_rate), f.pBat.survival_rate) : null
    const bowlEconP = f.pBowl ? pctileInv(f.bowl?.map(d => d.economy_index), f.pBowl.economy_index) : null
    const bowlSRP = f.pBowl ? pctileInv(f.bowl?.map(d => d.strike_rate), f.pBowl.strike_rate) : null
    return {
      label: f.label,
      color: f.color,
      values: [batScoreP, batSurvP, bowlEconP, bowlSRP]
    }
  })
}
Show code
// ── Radar Chart — format comparison ─────────────────────────
{
  if (!playerData || !playerBio || radarFormats.length === 0) return html``

  const n = radarAxes.length
  const cx = 250, cy = 200, R = 130
  const ns = "http://www.w3.org/2000/svg"

  const svg = document.createElementNS(ns, "svg")
  svg.setAttribute("viewBox", "0 0 500 400")
  svg.setAttribute("class", "skill-radar")

  function angle(i) { return (Math.PI * 2 * i / n) - Math.PI / 2 }
  function pt(i, r) {
    const a = angle(i)
    return [cx + r * Math.cos(a), cy + r * Math.sin(a)]
  }

  // Concentric rings
  for (const pct of [0.25, 0.5, 0.75, 1.0]) {
    const r = R * pct
    const points = Array.from({ length: n }, (_, i) => pt(i, r).join(",")).join(" ")
    const ring = document.createElementNS(ns, "polygon")
    ring.setAttribute("points", points)
    ring.setAttribute("class", "radar-ring")
    svg.appendChild(ring)
  }

  // Axis lines
  for (let i = 0; i < n; i++) {
    const [x, y] = pt(i, R)
    const line = document.createElementNS(ns, "line")
    line.setAttribute("x1", cx)
    line.setAttribute("y1", cy)
    line.setAttribute("x2", x)
    line.setAttribute("y2", y)
    line.setAttribute("class", "radar-axis")
    svg.appendChild(line)
  }

  // Draw one polygon per format
  for (const fmt of radarFormats) {
    const validPts = []
    for (let i = 0; i < n; i++) {
      const v = fmt.values[i]
      if (v != null) validPts.push(pt(i, R * v / 100))
      else validPts.push(pt(i, 0))
    }

    const polyPoints = validPts.map(p => p.join(",")).join(" ")
    const fill = document.createElementNS(ns, "polygon")
    fill.setAttribute("points", polyPoints)
    fill.setAttribute("class", "radar-fill")
    fill.setAttribute("fill", fmt.color + "25")
    fill.setAttribute("stroke", fmt.color)
    fill.setAttribute("stroke-width", "2")
    svg.appendChild(fill)

    // Dots
    for (let i = 0; i < n; i++) {
      if (fmt.values[i] == null) continue
      const [x, y] = validPts[i]
      const dot = document.createElementNS(ns, "circle")
      dot.setAttribute("cx", x)
      dot.setAttribute("cy", y)
      dot.setAttribute("r", 4)
      dot.setAttribute("fill", fmt.color)
      dot.setAttribute("class", "radar-dot")
      svg.appendChild(dot)
    }
  }

  // Labels — adjust text-anchor for left/right labels
  const labelPad = 22
  for (let i = 0; i < n; i++) {
    const [x, y] = pt(i, R + labelPad)
    const lbl = document.createElementNS(ns, "text")
    lbl.setAttribute("x", x)
    lbl.setAttribute("y", y)
    lbl.setAttribute("class", "radar-label")
    // Right-side labels: anchor start; left-side: anchor end; top/bottom: middle
    // Use style (not attribute) to override CSS .radar-label { text-anchor: middle }
    if (x > cx + 10) lbl.style.textAnchor = "start"
    else if (x < cx - 10) lbl.style.textAnchor = "end"
    lbl.textContent = radarAxes[i]
    svg.appendChild(lbl)
  }

  // Legend
  const legend = document.createElement("div")
  legend.className = "radar-legend"
  for (const fmt of radarFormats) {
    const item = document.createElement("span")
    item.className = "radar-legend-item"
    const swatch = document.createElement("span")
    swatch.className = "radar-legend-swatch"
    swatch.style.background = fmt.color
    item.appendChild(swatch)
    item.appendChild(document.createTextNode(fmt.label))
    legend.appendChild(item)
  }

  const wrap = document.createElement("div")
  wrap.className = "skill-radar-wrap"
  wrap.appendChild(svg)
  wrap.appendChild(legend)

  const heading = document.createElement("h2")
  heading.textContent = "Skill Profile"
  const outer = document.createElement("div")
  outer.appendChild(heading)
  outer.appendChild(wrap)
  return outer
}
Show code
// ── Format rating cards ──────────────────────────────────────
{
  if (!playerData || !playerBio) return html``

  const formats = [
    { label: "T20", bat: playerData.t20_bat, bowl: playerData.t20_bowl },
    { label: "ODI", bat: playerData.odi_bat, bowl: playerData.odi_bowl },
    { label: "Test", bat: playerData.test_bat, bowl: playerData.test_bowl }
  ].filter(f => f.bat || f.bowl)

  if (formats.length === 0) return html`<p class="text-muted">No rating data available for this player.</p>`

  function fmtVal(v, decimals) {
    if (v == null) return "—"
    return typeof v === "number" ? v.toFixed(decimals) : String(v)
  }

  function fmtInt(v) {
    if (v == null) return "—"
    return typeof v === "number" ? v.toLocaleString() : String(v)
  }

  const sections = formats.map(f => {
    const cards = []

    if (f.bat) {
      cards.push(html`<div style="margin-bottom:0.5rem">
        <span style="font-size:0.75rem;color:#8b929e;text-transform:uppercase;letter-spacing:0.05em">Batting</span>
      </div>
      <div class="season-summary">
        <div class="stat-card">
          <div class="stat-label">Scoring</div>
          <div class="stat-value">${fmtVal(f.bat.scoring_index, 4)}</div>
        </div>
        <div class="stat-card">
          <div class="stat-label">Survival</div>
          <div class="stat-value">${fmtVal(f.bat.survival_rate, 4)}</div>
        </div>
        <div class="stat-card">
          <div class="stat-label">Balls Faced</div>
          <div class="stat-value">${fmtInt(f.bat.balls_faced)}</div>
        </div>
      </div>`)
    }

    if (f.bowl) {
      cards.push(html`<div style="margin-bottom:0.5rem;${f.bat ? 'margin-top:1rem' : ''}">
        <span style="font-size:0.75rem;color:#8b929e;text-transform:uppercase;letter-spacing:0.05em">Bowling</span>
      </div>
      <div class="season-summary">
        <div class="stat-card">
          <div class="stat-label">Economy</div>
          <div class="stat-value">${fmtVal(f.bowl.economy_index, 4)}</div>
        </div>
        <div class="stat-card">
          <div class="stat-label">Strike Rate</div>
          <div class="stat-value">${fmtVal(f.bowl.strike_rate, 4)}</div>
        </div>
        <div class="stat-card">
          <div class="stat-label">Balls Bowled</div>
          <div class="stat-value">${fmtInt(f.bowl.balls_bowled)}</div>
        </div>
      </div>`)
    }

    return html`<h2 style="margin-top:1.5rem">${f.label}</h2>
    ${cards}`
  })

  return html`${sections}`
}
 

Pete Owen · Sydney · © 2026 · Source

Privacy | Disclaimer