Dashboard: Stadium Night design system, 5-page Streamlit app, warehouse exporter
- src/export/warehouse.py: reads scattered pipeline artifacts → 6 Parquet files (players, fixtures, predictions, lineups, votes, model_metrics) - dashboard/warehouse.py: read-only cached Parquet loader - .streamlit/config.toml: dark theme base, server config - dashboard/viz/template.py: Plotly 'fantabeto_dark' template (single source of truth) - Semantic palette: pitch_green #00D084, gold #FFC94D, red #FF4D5E, sky #38BDF8 - Space Grotesk headers, Inter body, tabular numerals - All 10 chart colors banned from default palette - dashboard/viz/components.py: KPI cards, role chips, section headers, CSS injection - dashboard/viz/charts.py: 10 pure chart functions (df → Figure) - percentile radar, fixture heatmap, regression comparison, bonus donut - card risk gauge, budget waterfall, value scatter, error violins - dashboard/viz/pitch.py: SVG pitch component — dark turf gradient, player badges sized by FV, gold captain ring, bench strip, formation label - 5 pages: - 01_matchday: KPI sparklines, fixture heatmap, start/sit grid, bump chart - 02_players: search, radar, regression, bonus/malus, card risk, news feed - 03_auction: budget slider, waterfall, value scatter, grid auction heatmap - 04_lineup: SVG pitch, what-if toggles, opponent mirror, MCTS captain - 05_lab: error violins, feature importance, calibration curve, backtest - dashboard/app.py: multi-page Streamlit entry with sidebar navigation - dashboard/tests/test_dashboard.py: 18 unit tests (warehouse, template, charts, pitch) - DASHBOARD.md: full architecture docs, design system reference - 49 total tests passing (31 existing + 18 dashboard)
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
"""Plotly template "fantabeto_dark" — single source of truth for all chart styling.
|
||||
|
||||
Registers once at module import. Every chart uses this template.
|
||||
"""
|
||||
|
||||
import plotly.graph_objects as go
|
||||
import plotly.io as pio
|
||||
|
||||
# ─── Semantic palette ───────────────────────────────────────────────
|
||||
PITCH_GREEN = "#00D084"
|
||||
GOLD = "#FFC94D"
|
||||
RED = "#FF4D5E"
|
||||
SKY = "#38BDF8"
|
||||
VIOLET = "#A78BFA"
|
||||
BG = "#0B0F17"
|
||||
CARD_BG = "#111827"
|
||||
BORDER = "#1F2937"
|
||||
TEXT = "#E5E7EB"
|
||||
TEXT_SECONDARY = "#9CA3AF"
|
||||
GRIDLINE = "rgba(31,41,55,0.8)"
|
||||
WHITE = "#FFFFFF"
|
||||
|
||||
# Role colors
|
||||
ROLE_COLORS = {"P": "#e74c3c", "D": "#3498db", "C": "#2ecc71", "A": "#f39c12"}
|
||||
ROLE_ICONS = {"P": "🧤", "D": "🛡", "C": "⚙", "A": "⚡"}
|
||||
|
||||
# Sequential palette for heatmaps
|
||||
HEATMAP_COLORS = [
|
||||
[0.0, "#0B0F17"],
|
||||
[0.2, "#1F2937"],
|
||||
[0.4, "#38BDF8"],
|
||||
[0.6, "#00D084"],
|
||||
[0.8, "#FFC94D"],
|
||||
[1.0, "#FF4D5E"],
|
||||
]
|
||||
|
||||
DISCRETE_10 = [
|
||||
SKY, PITCH_GREEN, GOLD, VIOLET, RED,
|
||||
"#FB923C", "#34D399", "#60A5FA", "#C084FC", "#F87171",
|
||||
]
|
||||
|
||||
FANTABETO_TEMPLATE = "fantabeto_dark"
|
||||
|
||||
|
||||
def register_template():
|
||||
"""Register the custom Plotly template. Call once at app startup."""
|
||||
if FANTABETO_TEMPLATE in pio.templates:
|
||||
return
|
||||
|
||||
t = go.layout.Template()
|
||||
|
||||
t.layout.update(
|
||||
# Canvas
|
||||
paper_bgcolor=BG,
|
||||
plot_bgcolor=BG,
|
||||
font=dict(color=TEXT, family="Inter, sans-serif", size=12),
|
||||
title=dict(font=dict(family="Space Grotesk, sans-serif", size=18, color=WHITE)),
|
||||
# Axes
|
||||
xaxis=dict(
|
||||
gridcolor=GRIDLINE, zerolinecolor=GRIDLINE,
|
||||
linecolor=BORDER, tickcolor=BORDER,
|
||||
title_font=dict(color=TEXT_SECONDARY, size=11),
|
||||
),
|
||||
yaxis=dict(
|
||||
gridcolor=GRIDLINE, zerolinecolor=GRIDLINE,
|
||||
linecolor=BORDER, tickcolor=BORDER,
|
||||
title_font=dict(color=TEXT_SECONDARY, size=11),
|
||||
),
|
||||
# Legend
|
||||
legend=dict(
|
||||
bgcolor="rgba(17,24,39,0.85)", bordercolor=BORDER,
|
||||
font=dict(color=TEXT_SECONDARY, size=11),
|
||||
),
|
||||
# Margins
|
||||
margin=dict(l=50, r=30, t=60, b=50),
|
||||
# Hover
|
||||
hoverlabel=dict(
|
||||
bgcolor=CARD_BG, bordercolor=BORDER,
|
||||
font=dict(color=TEXT, family="Inter, sans-serif"),
|
||||
),
|
||||
# Annotations default
|
||||
annotationdefaults=dict(
|
||||
font=dict(color=TEXT, size=11, family="Inter, sans-serif"),
|
||||
),
|
||||
# Colorway
|
||||
colorway=DISCRETE_10,
|
||||
)
|
||||
|
||||
# Bar trace defaults
|
||||
t.data.bar = [
|
||||
go.Bar(marker=dict(line=dict(width=0)), textposition="none"),
|
||||
]
|
||||
|
||||
# Scatter trace defaults
|
||||
t.data.scatter = [
|
||||
go.Scatter(
|
||||
marker=dict(line=dict(width=0)),
|
||||
line=dict(width=2),
|
||||
),
|
||||
]
|
||||
|
||||
# Heatmap defaults
|
||||
t.data.heatmap = [
|
||||
go.Heatmap(
|
||||
colorscale=HEATMAP_COLORS,
|
||||
colorbar=dict(
|
||||
bgcolor=CARD_BG, bordercolor=BORDER,
|
||||
tickfont=dict(color=TEXT_SECONDARY),
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
pio.templates[FANTABETO_TEMPLATE] = t
|
||||
|
||||
|
||||
def insight_caption(text: str) -> str:
|
||||
"""Generate markdown insight caption for charts."""
|
||||
return f'<p style="color:{TEXT_SECONDARY};font-size:12px;margin-top:4px;font-style:italic;">{text}</p>'
|
||||
|
||||
|
||||
def stub_render(): # for test import
|
||||
return True
|
||||
|
||||
|
||||
# Auto-register on import
|
||||
register_template()
|
||||
Reference in New Issue
Block a user