"""SVG pitch component — dark turf gradient, player badges, bench strip. Renders a vertical football pitch (105m × 68m) with players positioned according to their role and formation. Badge size = projected FV. Gold ring = captain. """ import streamlit.components.v1 as components from .template import PITCH_GREEN, GOLD, WHITE, RED, SKY, TEXT_SECONDARY, BG, CARD_BG, BORDER, ROLE_COLORS # Position templates by formation (player index -> [x%, y%]) FORMATION_POSITIONS = { "4-4-2": { "P": [(50, 92)], "D": [(15, 72), (38, 72), (62, 72), (85, 72)], "C": [(15, 48), (38, 48), (62, 48), (85, 48)], "A": [(35, 24), (65, 24)], }, "4-3-3": { "P": [(50, 92)], "D": [(15, 72), (38, 72), (62, 72), (85, 72)], "C": [(25, 48), (50, 48), (75, 48)], "A": [(20, 24), (50, 24), (80, 24)], }, "3-5-2": { "P": [(50, 92)], "D": [(25, 72), (50, 72), (75, 72)], "C": [(10, 48), (30, 48), (50, 48), (70, 48), (90, 48)], "A": [(35, 24), (65, 24)], }, "4-2-3-1": { "P": [(50, 92)], "D": [(15, 72), (38, 72), (62, 72), (85, 72)], "C": [(35, 55), (65, 55)], "A": [(50, 35)], # CAM + ST }, "3-4-3": { "P": [(50, 92)], "D": [(25, 72), (50, 72), (75, 72)], "C": [(15, 48), (38, 48), (62, 48), (85, 48)], "A": [(20, 24), (50, 24), (80, 24)], }, "3-4-2-1": { "P": [(50, 92)], "D": [(25, 72), (50, 72), (75, 72)], "C": [(15, 48), (38, 48), (62, 48), (85, 48)], "A": [(50, 35), (35, 22), (65, 22)], }, } def render_pitch( lineup: list[dict], formation: str = "4-4-2", captain: str = "", bench: list[dict] | None = None, width: int = 700, height: int = 600, ) -> str: """Generate SVG pitch with player badges. Args: lineup: list of {'name': str, 'role': str, 'fv': float, 'starter_pct': float} formation: e.g. "4-4-2" captain: player name to highlight with gold ring bench: optional bench players """ positions = FORMATION_POSITIONS.get(formation, FORMATION_POSITIONS["4-4-2"]) # Assign positions to players by role role_slots = {"P": 0, "D": 0, "C": 0, "A": 0} player_positions = [] for p in lineup: role = p.get("role", "C") idx = role_slots.get(role, 0) pos_list = positions.get(role, [(50, 50)]) if idx < len(pos_list): x, y = pos_list[idx] else: x, y = 50, 50 role_slots[role] = idx + 1 player_positions.append((p, x, y)) # Scale FV to badge size fvs = [p.get("fv", 6) for p in lineup] min_fv, max_fv = min(fvs), max(fvs) fv_range = max(max_fv - min_fv, 1) svg_parts = [f""" """] # Pitch markings svg_parts.append(f""" """) # Player badges for p, px, py in player_positions: fv = p.get("fv", 6) size_factor = 0.45 + 0.55 * (fv - min_fv) / fv_range radius = 18 * size_factor role = p.get("role", "C") color = ROLE_COLORS.get(role, SKY) name = p.get("name", "?") is_captain = name == captain starter_pct = p.get("starter_pct", 100) cx = px / 100 * width cy = py / 100 * height ring_color = GOLD if is_captain else color ring_width = 3 if is_captain else 1.5 svg_parts.append(f""" {fv:.1f} {name[:12]}{'...' if len(name)>12 else ''} """) if starter_pct < 70: svg_parts.append(f""" {starter_pct:.0f}% """) # Formation label svg_parts.append(f""" {formation} """) # Bench strip if bench and len(bench) > 0: bench_y = height * 0.99 svg_parts.append(f""" BENCH """) svg_parts.append("") return "\n".join(svg_parts) def show_pitch(lineup: list[dict], formation: str = "4-4-2", captain: str = "", bench: list[dict] | None = None, height: int = 600): """Render pitch as a Streamlit HTML component.""" svg = render_pitch(lineup, formation, captain, bench, height=height) components.html(svg, height=height + 20, scrolling=False)