65f5b66b05
- 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)
127 lines
3.4 KiB
Python
127 lines
3.4 KiB
Python
"""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()
|