{"spec_id":"line-win-probability","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nline-win-probability: Win Probability Chart\nLibrary: plotly 6.8.0 | Python 3.13.14\nQuality: 91/100 | Updated: 2026-06-21\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\n\n\n# Theme-adaptive chrome (Imprint palette)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\nGRID_RULE = \"rgba(26,26,23,0.30)\" if THEME == \"light\" else \"rgba(240,239,232,0.30)\"\n\n# Team colors — semantic exception: football teams carry strong real-world color identity.\n# Eagles (PHI) → Imprint brand green; Cowboys (DAL) → Imprint blue.\nHOME_COLOR = \"#009E73\"  # Imprint position 1 — Eagles green\nAWAY_COLOR = \"#4467A3\"  # Imprint position 3 — Cowboys blue\nHOME_FILL = \"rgba(0,158,115,0.22)\"\nAWAY_FILL = \"rgba(68,103,163,0.22)\"\n\n# Data — simulated NFL game: Eagles vs Cowboys\nnp.random.seed(42)\n\nplay_count = 120\nplays = np.arange(play_count)\n\nwin_prob = np.zeros(play_count)\nwin_prob[0] = 0.50\n\nq1_end, q2_end, q3_end = 30, 60, 90\n\n# Key scoring events (play_index, prob_shift, label)\nevents = [\n    (10, 0.15, \"Eagles TD\\n7-0\"),\n    (25, -0.10, \"Cowboys FG\\n7-3\"),\n    (40, 0.16, \"Eagles TD\\n14-3\"),\n    (55, -0.18, \"Cowboys TD\\n14-10\"),\n    (68, 0.12, \"Eagles FG\\n17-10\"),\n    (80, -0.22, \"Cowboys TD\\n17-17\"),\n    (98, 0.20, \"Eagles TD\\n24-17\"),\n    (112, 0.10, \"Eagles FG\\n27-17\"),\n]\n\nevent_plays = {e[0]: e[1] for e in events}\n\nfor i in range(1, play_count):\n    if i in event_plays:\n        drift = event_plays[i]\n    else:\n        drift = np.random.normal(0, 0.012)\n    win_prob[i] = np.clip(win_prob[i - 1] + drift, 0.03, 0.97)\n\nwin_prob[-1] = 1.0\nwin_prob[-2] = 0.96\nwin_prob[-3] = 0.92\n\nwin_pct = win_prob * 100\n\n# Plot\nfig = go.Figure()\n\n# Fill above 50% — home team (Eagles)\nwin_above = np.clip(win_pct, 50, 100)\nfig.add_trace(go.Scatter(x=plays, y=win_above, mode=\"lines\", line={\"width\": 0}, showlegend=False, hoverinfo=\"skip\"))\nfig.add_trace(\n    go.Scatter(\n        x=plays,\n        y=np.full(play_count, 50),\n        mode=\"lines\",\n        line={\"width\": 0},\n        fill=\"tonexty\",\n        fillcolor=HOME_FILL,\n        showlegend=False,\n        hoverinfo=\"skip\",\n    )\n)\n\n# Fill below 50% — away team (Cowboys)\nwin_below = np.clip(win_pct, 0, 50)\nfig.add_trace(go.Scatter(x=plays, y=win_below, mode=\"lines\", line={\"width\": 0}, showlegend=False, hoverinfo=\"skip\"))\nfig.add_trace(\n    go.Scatter(\n        x=plays,\n        y=np.full(play_count, 50),\n        mode=\"lines\",\n        line={\"width\": 0},\n        fill=\"tonexty\",\n        fillcolor=AWAY_FILL,\n        showlegend=False,\n        hoverinfo=\"skip\",\n    )\n)\n\n# Main win probability line\nfig.add_trace(\n    go.Scatter(\n        x=plays,\n        y=win_pct,\n        mode=\"lines\",\n        line={\"width\": 2.5, \"color\": INK, \"shape\": \"spline\", \"smoothing\": 0.8},\n        name=\"Win Probability\",\n        hovertemplate=\"Play %{x}<br>Win Prob: %{y:.1f}%<extra></extra>\",\n    )\n)\n\n# 50% reference line\nfig.add_hline(y=50, line_dash=\"dash\", line_color=INK_SOFT, line_width=1.5, opacity=0.6)\n\n# Quarter dividers and labels\nfor q_play, q_label in [(q1_end, \"Q2\"), (q2_end, \"Q3\"), (q3_end, \"Q4\")]:\n    fig.add_vline(x=q_play, line_dash=\"dot\", line_color=GRID_RULE, line_width=1.5)\n    fig.add_annotation(\n        x=q_play, y=100, text=f\"<b>{q_label}</b>\", showarrow=False, font={\"size\": 10, \"color\": INK_MUTED}, yshift=14\n    )\n\nfig.add_annotation(x=0, y=100, text=\"<b>Q1</b>\", showarrow=False, font={\"size\": 10, \"color\": INK_MUTED}, yshift=14)\n\n# Scoring event markers and annotations\nfor play_idx, _, label in events:\n    label_clean = label.replace(\"\\n\", \"<br>\")\n    is_home = \"Eagles\" in label\n    marker_color = HOME_COLOR if is_home else AWAY_COLOR\n    y_val = win_pct[play_idx]\n\n    fig.add_trace(\n        go.Scatter(\n            x=[play_idx],\n            y=[y_val],\n            mode=\"markers\",\n            marker={\"size\": 10, \"color\": marker_color, \"line\": {\"color\": PAGE_BG, \"width\": 2}},\n            showlegend=False,\n            hovertemplate=f\"{label_clean}<br>Play {play_idx}<br>Win Prob: {y_val:.1f}%<extra></extra>\",\n        )\n    )\n\n    ay_offset = -55 if y_val > 55 else 55\n    ax_offset = 0\n    if play_idx == 10:\n        ax_offset = 60  # steer right to avoid PHI Eagles legend in upper-left\n        ay_offset = -60\n    elif play_idx == 68:\n        ax_offset = 55\n        ay_offset = -45\n    elif play_idx == 80:\n        ax_offset = -55\n        ay_offset = 50\n    elif play_idx == 98:\n        ax_offset = 45\n        ay_offset = -55\n    elif play_idx == 112:\n        ax_offset = 55\n        ay_offset = -40\n\n    fig.add_annotation(\n        x=play_idx,\n        y=y_val,\n        text=f\"<b>{label_clean}</b>\",\n        showarrow=True,\n        arrowhead=2,\n        arrowwidth=1.5,\n        arrowcolor=marker_color,\n        ax=ax_offset,\n        ay=ay_offset,\n        font={\"size\": 10, \"color\": marker_color},\n        bgcolor=ELEVATED_BG,\n        bordercolor=marker_color,\n        borderwidth=1.5,\n        borderpad=5,\n    )\n\n# Team legend\nfig.add_annotation(\n    x=0.01,\n    y=0.97,\n    xref=\"paper\",\n    yref=\"paper\",\n    text=\"<b>▲ PHI Eagles</b>\",\n    showarrow=False,\n    font={\"size\": 12, \"color\": HOME_COLOR},\n    bgcolor=ELEVATED_BG,\n    bordercolor=HOME_COLOR,\n    borderwidth=1,\n    borderpad=6,\n)\nfig.add_annotation(\n    x=0.01,\n    y=0.04,\n    xref=\"paper\",\n    yref=\"paper\",\n    text=\"<b>▼ DAL Cowboys</b>\",\n    showarrow=False,\n    font={\"size\": 12, \"color\": AWAY_COLOR},\n    bgcolor=ELEVATED_BG,\n    bordercolor=AWAY_COLOR,\n    borderwidth=1,\n    borderpad=6,\n)\n\n# Final score — upper right corner inside the plot\nfig.add_annotation(\n    x=0.99,\n    y=0.97,\n    xref=\"paper\",\n    yref=\"paper\",\n    xanchor=\"right\",\n    yanchor=\"top\",\n    text=\"Final Score: Eagles 27 – Cowboys 17\",\n    showarrow=False,\n    font={\"size\": 12, \"color\": INK_SOFT},\n    bgcolor=ELEVATED_BG,\n    borderpad=5,\n)\n\n# Layout\nfig.update_layout(\n    autosize=False,\n    title={\n        \"text\": \"line-win-probability · python · plotly · anyplot.ai\",\n        \"font\": {\"size\": 16, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n        \"y\": 0.97,\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    xaxis={\n        \"title\": {\"text\": \"Play Number\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"showline\": True,\n        \"linewidth\": 1,\n        \"linecolor\": INK_SOFT,\n        \"showgrid\": False,\n        \"range\": [-2, play_count + 2],\n    },\n    yaxis={\n        \"title\": {\"text\": \"Win Probability (%)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"tickvals\": [0, 25, 50, 75, 100],\n        \"ticktext\": [\"0%\", \"25%\", \"50%\", \"75%\", \"100%\"],\n        \"range\": [0, 100],\n        \"showline\": True,\n        \"linewidth\": 1,\n        \"linecolor\": INK_SOFT,\n        \"showgrid\": True,\n        \"gridwidth\": 1,\n        \"gridcolor\": GRID,\n    },\n    showlegend=False,\n    margin={\"l\": 80, \"r\": 40, \"t\": 100, \"b\": 70},\n    hovermode=\"x unified\",\n)\n\nfig.update_xaxes(showspikes=True, spikecolor=INK_SOFT, spikethickness=1, spikedash=\"dot\")\n\n# Save — landscape 3200×1800 (width=800, height=450, scale=4)\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}