{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# College baseball and softball\n",
    "\n",
    "Nine worked examples on the 2026 college season: the Men's College World Series in Omaha, the Women's College World\n",
    "Series champion's run, the SEC in both sports, and every team in ESPN's Division I softball standings. All data comes\n",
    "from ESPN's college-baseball and college-softball endpoints through\n",
    "[sportsdataverse-py](https://py.sportsdataverse.org/); sdvplot's `ncaa_baseball` and `ncaa_softball` leagues key on\n",
    "ESPN's team ids, so the data's ids go straight in."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1",
   "metadata": {},
   "outputs": [],
   "source": [
    "import datetime as dt\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import polars as pl\n",
    "import sportsdataverse as sdv\n",
    "\n",
    "import sdvplot\n",
    "\n",
    "SEASON = 2026  # the 2026 season ended with the World Series in June\n",
    "ESPN = \"Data: ESPN via sportsdataverse-py\"\n",
    "\n",
    "\n",
    "def scoreboard(sport: str, start: dt.date, days: int) -> pl.DataFrame:\n",
    "    \"\"\"Every game on ESPN's college scoreboard for `days` dates from `start` (the endpoint takes one date per call).\"\"\"\n",
    "    fetch = getattr(sdv, f\"espn_college_{sport}_scoreboard\")\n",
    "    frames = [fetch(dates=(start + dt.timedelta(days=d)).strftime(\"%Y%m%d\")) for d in range(days)]\n",
    "    return pl.concat([f for f in frames if f.height], how=\"diagonal_relaxed\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2",
   "metadata": {},
   "source": [
    "The 2026 Men's College World Series ran from June 12 to June 22. Each scoreboard row is one game, with both teams'\n",
    "ESPN ids, abbreviations and scores, and ESPN's note naming the round."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3",
   "metadata": {},
   "outputs": [],
   "source": [
    "cws = (\n",
    "    scoreboard(\"baseball\", dt.date(SEASON, 6, 12), 11)\n",
    "    .filter(pl.col(\"note\").str.contains(\"College World Series\"))\n",
    "    .with_columns(\n",
    "        pl.col(\"home_score\").cast(pl.Int64),\n",
    "        pl.col(\"away_score\").cast(pl.Int64),\n",
    "        local=pl.col(\"date\")\n",
    "        .str.strptime(pl.Datetime, \"%Y-%m-%dT%H:%MZ\")\n",
    "        .dt.replace_time_zone(\"UTC\")\n",
    "        .dt.convert_time_zone(\"America/Chicago\"),\n",
    "    )\n",
    "    .sort(\"local\")\n",
    ")\n",
    "cws.select(\"local\", \"note\", \"away_abbreviation\", \"away_score\", \"home_abbreviation\", \"home_score\").tail(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4",
   "metadata": {},
   "source": [
    "## 1. The College World Series, game by game\n",
    "\n",
    "A great_tables results table: `gt_sdv_logos` turns the winner and loser id columns into logos, and `gt_theme_ncaa`\n",
    "gives it the NCAA stats-site look."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5",
   "metadata": {},
   "outputs": [],
   "source": [
    "from great_tables import GT\n",
    "\n",
    "from sdvplot.great_tables import gt_sdv_logos, gt_theme_ncaa\n",
    "\n",
    "home_won = pl.col(\"home_score\") > pl.col(\"away_score\")\n",
    "results = cws.select(\n",
    "    date=pl.col(\"local\").dt.strftime(\"%b %d\"),\n",
    "    round=pl.col(\"note\").str.replace(r\"^Men's College World Series( - )?\", \"\"),\n",
    "    winner_logo=pl.when(home_won).then(\"home_id\").otherwise(\"away_id\"),\n",
    "    winner=pl.when(home_won).then(\"home_display_name\").otherwise(\"away_display_name\"),\n",
    "    score=pl.format(\n",
    "        \"{}-{}\", pl.max_horizontal(\"home_score\", \"away_score\"), pl.min_horizontal(\"home_score\", \"away_score\")\n",
    "    ),\n",
    "    loser_logo=pl.when(home_won).then(\"away_id\").otherwise(\"home_id\"),\n",
    "    loser=pl.when(home_won).then(\"away_display_name\").otherwise(\"home_display_name\"),\n",
    ")\n",
    "gt = gt_theme_ncaa(\n",
    "    GT(results)\n",
    "    .cols_label(\n",
    "        date=\"Date\", round=\"Round\", winner_logo=\"\", winner=\"Winner\", score=\"Score\", loser_logo=\"\", loser=\"Loser\"\n",
    "    )\n",
    "    .cols_align(\"center\", columns=\"score\")\n",
    "    .tab_header(title=f\"{SEASON} Men's College World Series\", subtitle=\"Charles Schwab Field, Omaha\")\n",
    "    .tab_source_note(ESPN)\n",
    ")\n",
    "gt_sdv_logos(gt, [\"winner_logo\", \"loser_logo\"], league=\"ncaa_baseball\", height=26)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6",
   "metadata": {},
   "source": [
    "## 2. Run differential in Omaha\n",
    "\n",
    "Stack the home and away sides into one row per team per game, then total each team's runs for and against. The bar\n",
    "colors come from `team_colors` with ESPN ids, and `axis_logos` replaces the id tick labels with logos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7",
   "metadata": {
    "sdvplot_gallery": {
     "alt": "Bar chart of each 2026 Men's College World Series team's run differential in Omaha, with logos and records.",
     "title": "College World Series run differential"
    },
    "tags": [
     "gallery"
    ]
   },
   "outputs": [],
   "source": [
    "sides = pl.concat(\n",
    "    [\n",
    "        cws.select(team=\"home_id\", rf=\"home_score\", ra=\"away_score\"),\n",
    "        cws.select(team=\"away_id\", rf=\"away_score\", ra=\"home_score\"),\n",
    "    ]\n",
    ")\n",
    "omaha = (\n",
    "    sides.group_by(\"team\", maintain_order=True)\n",
    "    .agg(games=pl.len(), wins=(pl.col(\"rf\") > pl.col(\"ra\")).sum(), diff=(pl.col(\"rf\") - pl.col(\"ra\")).sum())\n",
    "    .sort([\"diff\", \"wins\", \"team\"], descending=[True, True, False])\n",
    ")\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(8, 5))\n",
    "ax.bar(omaha[\"team\"], omaha[\"diff\"], color=sdvplot.team_colors(omaha[\"team\"].to_list(), \"ncaa_baseball\"))\n",
    "for i, (diff, wins, games) in enumerate(omaha.select(\"diff\", \"wins\", \"games\").iter_rows()):\n",
    "    ax.text(\n",
    "        i,\n",
    "        diff + (0.6 if diff >= 0 else -0.6),\n",
    "        f\"{wins}-{games - wins}\",\n",
    "        ha=\"center\",\n",
    "        va=\"bottom\" if diff >= 0 else \"top\",\n",
    "        fontsize=9,\n",
    "    )\n",
    "ax.axhline(0, color=\"#222222\", linewidth=0.8)\n",
    "ax.set_ylabel(\"Run differential in the CWS\")\n",
    "ax.spines[[\"top\", \"right\"]].set_visible(False)\n",
    "ax.set_title(f\"{SEASON} Men's College World Series: run differential and record\", loc=\"left\", fontweight=\"bold\")\n",
    "fig.subplots_adjust(bottom=0.16)\n",
    "fig.text(0.99, 0.01, ESPN, ha=\"right\", fontsize=8, color=\"#666666\")\n",
    "sdvplot.axis_logos(ax, \"x\", league=\"ncaa_baseball\", height=0.1)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8",
   "metadata": {},
   "source": [
    "## 3. The clincher, inning by inning\n",
    "\n",
    "ESPN's game summary carries each team's line score. Cumulative runs by inning, in team colors, with each team's logo at\n",
    "the end of its line."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9",
   "metadata": {
    "sdvplot_gallery": {
     "alt": "Step chart of cumulative runs by inning in the 2026 Men's College World Series clinching game, with team logos at the line ends.",
     "title": "College World Series final, inning by inning"
    },
    "tags": [
     "gallery"
    ]
   },
   "outputs": [],
   "source": [
    "final = cws.row(-1, named=True)\n",
    "summary = sdv.espn_college_baseball_summary(event_id=final[\"game_id\"], return_parsed=False)\n",
    "line = pl.DataFrame(\n",
    "    [\n",
    "        {\"team\": c[\"team\"][\"id\"], \"inning\": i + 1, \"runs\": int(s[\"displayValue\"]) if s[\"displayValue\"].isdigit() else 0}\n",
    "        for c in summary[\"header\"][\"competitions\"][0][\"competitors\"]\n",
    "        for i, s in enumerate(c[\"linescores\"])\n",
    "    ]\n",
    ")\n",
    "line = pl.concat([line.select(\"team\").unique().with_columns(inning=0, runs=0), line], how=\"vertical_relaxed\")\n",
    "line = line.sort(\"team\", \"inning\").with_columns(total=pl.col(\"runs\").cum_sum().over(\"team\"))\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(8, 5))\n",
    "ends = line.group_by(\"team\", maintain_order=True).agg(pl.col(\"inning\").max(), pl.col(\"total\").last())\n",
    "for team in ends[\"team\"]:\n",
    "    t = line.filter(pl.col(\"team\") == team)\n",
    "    ax.step(t[\"inning\"], t[\"total\"], where=\"post\", linewidth=3, color=sdvplot.team_colors(team, \"ncaa_baseball\"))\n",
    "ax.set_xticks(range(1, line[\"inning\"].max() + 1))\n",
    "ax.set_xlim(0, line[\"inning\"].max() + 1.2)\n",
    "ax.set_ylim(-0.5, line[\"total\"].max() + 1.5)\n",
    "ax.set_xlabel(\"Inning\")\n",
    "ax.set_ylabel(\"Runs\")\n",
    "ax.spines[[\"top\", \"right\"]].set_visible(False)\n",
    "fig.suptitle(final[\"note\"], x=0.125, ha=\"left\", fontsize=10, color=\"#555555\")\n",
    "ax.set_title(\n",
    "    f\"{final['away_location']} {final['away_score']}, {final['home_location']} {final['home_score']}\",\n",
    "    loc=\"left\",\n",
    "    fontweight=\"bold\",\n",
    ")\n",
    "fig.text(0.99, 0.01, ESPN, ha=\"right\", fontsize=8, color=\"#666666\")\n",
    "sdvplot.add_logos(\n",
    "    ax, (ends[\"inning\"] + 0.6).to_list(), ends[\"total\"].to_list(), ends[\"team\"], league=\"ncaa_baseball\", height=0.13\n",
    ")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10",
   "metadata": {},
   "source": [
    "## 4. The Women's College World Series champion's run\n",
    "\n",
    "The WCWS in Oklahoma City ran from May 28 to June 4. The champion is the winner of the series' last game; its games go into a\n",
    "table where `gt_color_results` fills each row by the result. As with any fill, the theme goes on first and the\n",
    "result colors after it. `logo_url` puts the champion's logo in the title."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11",
   "metadata": {},
   "outputs": [],
   "source": [
    "from great_tables import html\n",
    "\n",
    "from sdvplot.great_tables import gt_color_results\n",
    "\n",
    "wcws = (\n",
    "    scoreboard(\"softball\", dt.date(SEASON, 5, 28), 8)\n",
    "    .filter(pl.col(\"note\").str.contains(\"College World Series\"))\n",
    "    .with_columns(pl.col(\"home_score\").cast(pl.Int64), pl.col(\"away_score\").cast(pl.Int64))\n",
    "    .sort(\"date\", maintain_order=True)\n",
    ")\n",
    "last = wcws.row(-1, named=True)\n",
    "won_home = last[\"home_score\"] > last[\"away_score\"]\n",
    "champ, champ_name = (\n",
    "    (last[\"home_id\"], last[\"home_display_name\"]) if won_home else (last[\"away_id\"], last[\"away_display_name\"])\n",
    ")\n",
    "\n",
    "at_home = pl.col(\"home_id\") == champ\n",
    "run = (\n",
    "    wcws.filter(at_home | (pl.col(\"away_id\") == champ))\n",
    "    .select(\n",
    "        round=pl.col(\"note\").str.replace(r\"^Women's College World Series( - )?\", \"\"),\n",
    "        opponent_logo=pl.when(at_home).then(\"away_id\").otherwise(\"home_id\"),\n",
    "        opponent=pl.when(at_home).then(\"away_display_name\").otherwise(\"home_display_name\"),\n",
    "        rf=pl.when(at_home).then(\"home_score\").otherwise(\"away_score\"),\n",
    "        ra=pl.when(at_home).then(\"away_score\").otherwise(\"home_score\"),\n",
    "    )\n",
    "    .with_columns(result=pl.when(pl.col(\"rf\") > pl.col(\"ra\")).then(pl.lit(\"W\")).otherwise(pl.lit(\"L\")))\n",
    ")\n",
    "\n",
    "logo = sdvplot.logo_url(champ, \"ncaa_softball\")\n",
    "gt = gt_theme_ncaa(\n",
    "    GT(run.select(\"round\", \"opponent_logo\", \"opponent\", \"rf\", \"ra\", \"result\"))\n",
    "    .cols_label(round=\"Round\", opponent_logo=\"\", opponent=\"Opponent\", rf=\"Runs\", ra=\"Allowed\", result=\"\")\n",
    "    .tab_header(\n",
    "        title=html(f'<img src=\"{logo}\" style=\"height:40px;vertical-align:middle\"> {champ_name}'),\n",
    "        subtitle=f\"{SEASON} Women's College World Series\",\n",
    "    )\n",
    "    .tab_source_note(ESPN)\n",
    ")\n",
    "gt = gt_sdv_logos(gt, \"opponent_logo\", league=\"ncaa_softball\", height=26)\n",
    "gt_color_results(gt, \"result\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12",
   "metadata": {},
   "source": [
    "## 5. One conference: the SEC in college baseball\n",
    "\n",
    "ESPN's standings take a `group` for one conference (27 is the SEC in college baseball). plotnine's `geom_sdv_logos`\n",
    "plots runs scored against runs allowed per game, and `geom_mean_lines` adds the conference averages. The y axis is\n",
    "reversed, so the best run prevention sits on top."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13",
   "metadata": {},
   "outputs": [],
   "source": [
    "from plotnine import aes, ggplot, labs, scale_x_continuous, scale_y_reverse, theme, theme_bw\n",
    "\n",
    "from sdvplot.plotnine import geom_mean_lines, geom_sdv_logos\n",
    "\n",
    "sec = sdv.espn_college_baseball_standings(season=SEASON, group=27).with_columns(\n",
    "    rs_g=pl.col(\"points_for\") / pl.col(\"games_played\"), ra_g=pl.col(\"points_against\") / pl.col(\"games_played\")\n",
    ")\n",
    "(\n",
    "    ggplot(sec.to_pandas(), aes(\"rs_g\", \"ra_g\", team=\"team_id\", x0=\"rs_g\", y0=\"ra_g\"))\n",
    "    + geom_mean_lines(color=\"#888888\")\n",
    "    + geom_sdv_logos(league=\"ncaa_baseball\", height=0.1)\n",
    "    + scale_x_continuous(expand=(0.08, 0))\n",
    "    + scale_y_reverse(expand=(0.08, 0))\n",
    "    + labs(\n",
    "        x=\"Runs scored per game\",\n",
    "        y=\"Runs allowed per game (reversed)\",\n",
    "        title=f\"SEC baseball, {SEASON}: run scoring and prevention\",\n",
    "        caption=ESPN,\n",
    "    )\n",
    "    + theme_bw()\n",
    "    + theme(figure_size=(8, 6))\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14",
   "metadata": {},
   "source": [
    "## 6. The best records in Division I (interactive)\n",
    "\n",
    "The full Division I standings, top 15 by winning percentage, as an Altair bar chart. `palette` colors the bars by team,\n",
    "`axis_logos` replaces the team axis with logos, and the tooltip names the team."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "15",
   "metadata": {},
   "outputs": [],
   "source": [
    "import altair as alt\n",
    "\n",
    "top = (\n",
    "    sdv.espn_college_baseball_standings(season=SEASON)\n",
    "    .filter(pl.col(\"games_played\") >= 40)\n",
    "    .sort(\"win_percent\", descending=True, maintain_order=True)\n",
    "    .head(15)\n",
    "    .with_columns(record=pl.format(\"{}-{}\", pl.col(\"wins\").cast(pl.Int64), pl.col(\"losses\").cast(pl.Int64)))\n",
    ")\n",
    "colors = sdvplot.palette(\"ncaa_baseball\", teams=top[\"team_id\"])\n",
    "bars = (\n",
    "    alt.Chart(top.select(\"team_id\", \"team_display_name\", \"record\", \"win_percent\").to_pandas())\n",
    "    .mark_bar()\n",
    "    .encode(\n",
    "        x=alt.X(\"win_percent:Q\", title=\"Winning percentage\", axis=alt.Axis(format=\".0%\")),\n",
    "        y=alt.Y(\"team_id:N\", sort=top[\"team_id\"].to_list(), title=None),  # an explicit order survives the logo layer\n",
    "        color=alt.Color(\"team_id:N\", scale=alt.Scale(domain=list(colors), range=list(colors.values())), legend=None),\n",
    "        tooltip=[alt.Tooltip(\"team_display_name\", title=\"Team\"), alt.Tooltip(\"record\", title=\"Record\")],\n",
    "    )\n",
    "    .properties(width=480, height=420, title=f\"Best records in Division I baseball, {SEASON} (40+ games)\")\n",
    ")\n",
    "sdvplot.axis_logos(bars, \"y\", league=\"ncaa_baseball\", height=0.055)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16",
   "metadata": {},
   "source": [
    "## 7. One school, two sports\n",
    "\n",
    "The same school has a different ESPN id in each sport (Texas is 126 in college baseball and 538 in softball), so\n",
    "`ncaa_baseball` and `ncaa_softball` are separate leagues in sdvplot. To compare a school across sports, join the two\n",
    "standings on ESPN's abbreviation, which both sports share; 32 is the SEC's group in college softball. Vanderbilt has no\n",
    "softball team, so 15 schools match. The abbreviation also resolves in each sdvplot league, to that sport's id: ESPN's\n",
    "baseball teams list calls Missouri `MIZZ` while its standings and scoreboards say `MIZ`, and the index carries both."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17",
   "metadata": {},
   "outputs": [],
   "source": [
    "sec_sb = sdv.espn_college_softball_standings(season=SEASON, group=32)\n",
    "assert sec.schema[\"team_abbreviation\"] == sec_sb.schema[\"team_abbreviation\"]\n",
    "both = sec.select(\"team_abbreviation\", \"team_id\", baseball=\"win_percent\").join(\n",
    "    sec_sb.select(\"team_abbreviation\", softball_id=\"team_id\", softball=\"win_percent\"),\n",
    "    on=\"team_abbreviation\",\n",
    "    maintain_order=\"left\",\n",
    ")\n",
    "both = both.with_columns(\n",
    "    baseball_key=sdvplot.resolve(both[\"team_abbreviation\"], \"ncaa_baseball\", season=SEASON),\n",
    "    softball_key=sdvplot.resolve(both[\"team_abbreviation\"], \"ncaa_softball\", season=SEASON),\n",
    ")\n",
    "assert (both[\"baseball_key\"] == both[\"team_id\"]).all() and (both[\"softball_key\"] == both[\"softball_id\"]).all()\n",
    "both.filter(pl.col(\"team_abbreviation\").is_in([\"TEX\", \"OU\", \"MIZ\"])).select(\n",
    "    \"team_abbreviation\", \"team_id\", \"baseball_key\", \"softball_id\", \"softball_key\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18",
   "metadata": {
    "sdvplot_gallery": {
     "alt": "Scatter of SEC schools' 2026 baseball winning percentage against softball winning percentage, drawn as logos.",
     "title": "SEC schools in baseball and softball"
    },
    "tags": [
     "gallery"
    ]
   },
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(figsize=(7, 6))\n",
    "ax.plot([0.3, 0.9], [0.3, 0.9], color=\"#999999\", linestyle=\"--\", linewidth=1)\n",
    "ax.set_xlim(0.3, 0.9)\n",
    "ax.set_ylim(0.3, 0.9)\n",
    "ax.set_xlabel(\"Baseball winning percentage\")\n",
    "ax.set_ylabel(\"Softball winning percentage\")\n",
    "ax.text(0.32, 0.88, \"Better in softball\", fontsize=9, color=\"#555555\", va=\"top\")\n",
    "ax.text(0.88, 0.32, \"Better in baseball\", fontsize=9, color=\"#555555\", ha=\"right\")\n",
    "ax.set_title(f\"SEC schools in baseball and softball, {SEASON}\", loc=\"left\", fontweight=\"bold\")\n",
    "fig.text(0.99, 0.01, ESPN, ha=\"right\", fontsize=8, color=\"#666666\")\n",
    "sdvplot.add_logos(ax, both[\"baseball\"], both[\"softball\"], both[\"team_id\"], league=\"ncaa_baseball\", height=0.065)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19",
   "metadata": {},
   "source": [
    "## 8. Team colors with seaborn\n",
    "\n",
    "`palette` returns a plain `{team: color}` dict, which seaborn takes as is. College teams carry one ESPN color: the\n",
    "secondary is `None`, so a second color has to come from elsewhere."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20",
   "metadata": {},
   "outputs": [],
   "source": [
    "import seaborn as sns\n",
    "\n",
    "sb = sec_sb.sort(\"win_percent\", descending=True, maintain_order=True)\n",
    "fig, ax = plt.subplots(figsize=(9, 5))\n",
    "sns.barplot(\n",
    "    sb.to_pandas(),\n",
    "    x=\"team_id\",\n",
    "    y=\"win_percent\",\n",
    "    hue=\"team_id\",\n",
    "    order=sb[\"team_id\"].to_list(),\n",
    "    palette=sdvplot.palette(\"ncaa_softball\", teams=sb[\"team_id\"]),\n",
    "    saturation=1,  # seaborn mutes bar colors by default; keep the teams' own\n",
    "    legend=False,\n",
    "    ax=ax,\n",
    ")\n",
    "ax.set_xlabel(\"\")\n",
    "ax.set_ylabel(\"Winning percentage\")\n",
    "ax.spines[[\"top\", \"right\"]].set_visible(False)\n",
    "ax.set_title(f\"SEC softball, {SEASON}: winning percentage\", loc=\"left\", fontweight=\"bold\")\n",
    "fig.subplots_adjust(bottom=0.16)\n",
    "fig.text(0.99, 0.01, ESPN, ha=\"right\", fontsize=8, color=\"#666666\")\n",
    "sdvplot.axis_logos(ax, \"x\", league=\"ncaa_softball\", height=0.08)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21",
   "metadata": {},
   "outputs": [],
   "source": [
    "sdvplot.team_colors(sb[\"team_id\"].head(3).to_list(), \"ncaa_softball\", which=\"secondary\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22",
   "metadata": {},
   "source": [
    "## 9. Every Division I softball team (interactive)\n",
    "\n",
    "All teams in ESPN's Division I softball standings: runs scored against runs allowed per game, one logo each through\n",
    "the Plotly adapter. Hover a logo for the team and its record; the y axis runs high to low, so the best teams sit top\n",
    "right."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23",
   "metadata": {},
   "outputs": [],
   "source": [
    "import plotly.graph_objects as go\n",
    "\n",
    "d1 = (\n",
    "    sdv.espn_college_softball_standings(season=SEASON)\n",
    "    .filter(pl.col(\"games_played\") > 0, pl.col(\"points_for\") > 0)  # a few rows carry no run totals\n",
    "    .with_columns(\n",
    "        rs_g=pl.col(\"points_for\") / pl.col(\"games_played\"),\n",
    "        ra_g=pl.col(\"points_against\") / pl.col(\"games_played\"),\n",
    "        label=pl.format(\n",
    "            \"{} ({}-{})\", \"team_display_name\", pl.col(\"wins\").cast(pl.Int64), pl.col(\"losses\").cast(pl.Int64)\n",
    "        ),\n",
    "    )\n",
    ")\n",
    "fig = go.Figure(\n",
    "    go.Scatter(\n",
    "        x=d1[\"rs_g\"],\n",
    "        y=d1[\"ra_g\"],\n",
    "        mode=\"markers\",\n",
    "        marker={\"opacity\": 0},\n",
    "        text=d1[\"label\"],\n",
    "        hovertemplate=\"%{text}<br>%{x:.2f} scored, %{y:.2f} allowed per game<extra></extra>\",\n",
    "    )\n",
    ")\n",
    "fig = sdvplot.add_logos(fig, d1[\"rs_g\"], d1[\"ra_g\"], d1[\"team_id\"], league=\"ncaa_softball\", height=0.05)\n",
    "fig.update_layout(\n",
    "    title=f\"Division I softball, {SEASON}: runs per game<br><sup>{ESPN}</sup>\",\n",
    "    xaxis={\"title\": \"Runs scored per game\", \"range\": [d1[\"rs_g\"].min() - 0.5, d1[\"rs_g\"].max() + 0.5]},\n",
    "    yaxis={\"title\": \"Runs allowed per game\", \"range\": [d1[\"ra_g\"].max() + 0.5, d1[\"ra_g\"].min() - 0.5]},\n",
    "    width=800,\n",
    "    height=650,\n",
    "    template=\"plotly_white\",\n",
    ")\n",
    "fig"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "sdvplot": {
   "description": "College baseball and softball from ESPN: the College World Series in both sports, the SEC, one school across two sports, and every Division I softball team.",
   "label": "College baseball & softball",
   "position": 31
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
