Files
nprasad2077 11a5cc0f18
CI Health Check & Quality Gate / Code, dbt & Live App Health Check (pull_request) Successful in 1m11s
shot chart and navbar
2026-10-28 02:37:56 -05:00

176 lines
6.0 KiB
Python

import plotly.express as px
import plotly.graph_objects as go
import streamlit as st
from utils.charts import AMBER, MADE_EMERALD, MISS_CRIMSON, apply_arena_theme
from utils.db import query
st.title(":material/trending_up: Era Trends")
tab_scoring, tab_drama, tab_three = st.tabs(
[
":material/sports_basketball: Scoring & Pace",
":material/theater_comedy: Game Character",
":material/my_location: Three-Point Era",
]
)
with tab_scoring:
pace = query(
"""
SELECT s.season_display AS season, s.season_start_year,
round(avg(g.matchup_pace), 1) AS avg_pace,
round(avg(g.total_points), 1) AS avg_points,
round(avg(g.home_effective_fg_pct + g.visitor_effective_fg_pct) / 2 * 100, 1) AS avg_efg_pct
FROM main_intermediate.int_games_enriched g
JOIN main_marts.dim_seasons s ON g.season_start_year = s.season_start_year
GROUP BY s.season_display, s.season_start_year
ORDER BY s.season_start_year
"""
)
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=pace["season"],
y=pace["avg_points"],
name="Avg Points/Game",
mode="lines+markers",
line=dict(color=AMBER, width=3),
)
)
fig.add_trace(
go.Scatter(
x=pace["season"],
y=pace["avg_pace"],
name="Pace",
mode="lines+markers",
yaxis="y2",
line=dict(color=MADE_EMERALD, width=2, dash="dot"),
)
)
fig.update_layout(
title="Scoring & Pace Evolution",
yaxis=dict(title="Avg Points/Game"),
yaxis2=dict(title="Pace (possessions/48min)", overlaying="y", side="right"),
)
apply_arena_theme(fig, height=450)
st.plotly_chart(fig, width="stretch")
pace_clean = pace.dropna(subset=["avg_efg_pct"])
fig_eff = px.area(
pace_clean,
x="season",
y="avg_efg_pct",
title="League eFG% Trend",
color_discrete_sequence=[MADE_EMERALD],
)
fig_eff.update_layout(yaxis_title="eFG%")
apply_arena_theme(fig_eff, height=350)
st.plotly_chart(fig_eff, width="stretch")
with tab_drama:
drama = query(
"""
SELECT s.season_display AS season,
round(100.0 * sum(CASE WHEN g.is_overtime THEN 1 ELSE 0 END) / count(*), 1) AS ot_pct,
round(100.0 * sum(CASE WHEN g.point_differential <= 5 THEN 1 ELSE 0 END) / count(*), 1) AS clutch_pct,
round(100.0 * sum(CASE WHEN g.point_differential >= 20 THEN 1 ELSE 0 END) / count(*), 1) AS blowout_pct
FROM main_intermediate.int_games_enriched g
JOIN main_marts.dim_seasons s ON g.season_start_year = s.season_start_year
GROUP BY s.season_display, s.season_start_year
ORDER BY s.season_start_year
"""
)
fig = px.area(
drama,
x="season",
y=["clutch_pct", "blowout_pct", "ot_pct"],
title="Game Drama: Clutch (≤5pt margin) vs Blowouts (20+) vs Overtime",
color_discrete_sequence=[MADE_EMERALD, MISS_CRIMSON, AMBER],
)
fig.update_layout(yaxis_title="% of Games", legend_title="Category")
apply_arena_theme(fig, height=450)
st.plotly_chart(fig, width="stretch")
home = query(
"""
SELECT s.season_display AS season,
round(100.0 * sum(CASE WHEN g.is_home_team_winner THEN 1 ELSE 0 END) / count(*), 1) AS home_win_pct
FROM main_marts.fct_game_results g
JOIN main_marts.dim_seasons s ON g.season_key = s.season_key
GROUP BY s.season_display, s.season_start_year
ORDER BY s.season_start_year
"""
)
fig2 = px.line(
home,
x="season",
y="home_win_pct",
title="Home Court Advantage Over Time",
markers=True,
color_discrete_sequence=[AMBER],
)
fig2.update_layout(yaxis_range=[40, 70])
fig2.add_hline(y=50, line_dash="dash", line_color="gray", annotation_text="50%")
apply_arena_theme(fig2, height=400)
st.plotly_chart(fig2, width="stretch")
with tab_three:
three_pt = query(
"""
SELECT s.season_display AS season, s.season_start_year,
round(avg(three_point_rate) * 100, 1) AS avg_three_rate,
round(avg(CASE WHEN three_point_fg_pct > 0 THEN three_point_fg_pct ELSE NULL END) * 100, 1) AS avg_three_pct,
round(avg(three_point_attempts), 1) AS avg_3pa
FROM main_marts.fct_player_game_shooting sh
JOIN main_marts.dim_seasons s ON sh.season_key = s.season_key
WHERE sh.fg_attempts >= 5
GROUP BY s.season_display, s.season_start_year
ORDER BY s.season_start_year
"""
)
if not three_pt.empty:
fig = go.Figure()
fig.add_trace(
go.Bar(
x=three_pt["season"],
y=three_pt["avg_three_rate"],
name="3PA Rate (%)",
marker_color=AMBER,
opacity=0.75,
)
)
fig.add_trace(
go.Scatter(
x=three_pt["season"],
y=three_pt["avg_three_pct"],
name="3P Accuracy (%)",
mode="lines+markers",
yaxis="y2",
line=dict(color=MADE_EMERALD, width=3),
)
)
fig.update_layout(
title="The Three-Point Revolution: Volume vs Accuracy",
yaxis=dict(title="3PA Rate (% of all FGA)"),
yaxis2=dict(title="3P FG%", overlaying="y", side="right", range=[30, 50]),
barmode="overlay",
)
apply_arena_theme(fig, height=450)
st.plotly_chart(fig, width="stretch")
fig2 = px.area(
three_pt,
x="season",
y="avg_3pa",
title="Avg Three-Point Attempts per Player-Game",
color_discrete_sequence=[AMBER],
)
fig2.update_layout(yaxis_title="3PA per Game")
apply_arena_theme(fig2, height=350)
st.plotly_chart(fig2, width="stretch")
else:
st.info("Three-point shooting data not available.")