"""Inline SVG rendering helpers.""" from __future__ import annotations from html import escape from math import cos, pi, sin from pipelines.web.repository import ChartSeries, RadarSeries SERIES_STYLES = ( { "color": "#009e73", "dasharray": None, "marker": "circle", }, { "color": "#0072b2", "dasharray": "10 6", "marker": "square", }, { "color": "#e69f00", "dasharray": "4 5", "marker": "diamond", }, { "color": "#cc79a7", "dasharray": "14 5 3 5", "marker": "triangle", }, ) def render_score_history_svg(series: list[ChartSeries]) -> str: """Render a responsive inline SVG score history chart.""" width = 880 height = 300 margin_left = 70 margin_right = 28 margin_top = 24 margin_bottom = 48 plot_width = width - margin_left - margin_right plot_height = height - margin_top - margin_bottom all_years = sorted({point.year for item in series for point in item.points}) if not all_years: return _empty_svg(width, height, "No score history for this selection") min_year = min(all_years) max_year = max(all_years) year_span = max(max_year - min_year, 1) def x_for(year: int) -> float: return margin_left + ((year - min_year) / year_span) * plot_width def y_for(score: int) -> float: return margin_top + ((100 - score) / 100) * plot_height parts: list[str] = [ f'") return "".join(parts) def _empty_svg(width: int, height: int, message: str) -> str: return ( f'" ) def _tick_years(years: list[int]) -> list[int]: first = years[0] last = years[-1] start = first - (first % 5) tick_years = {year for year in range(start, last + 1, 5) if first <= year <= last} tick_years.add(first) tick_years.add(last) return sorted(tick_years) def render_compare_radar_svg(topics: list[str], series: list[RadarSeries]) -> str: """Render a server-side radar chart for legislator comparison.""" width = 720 height = 560 center_x = 285 center_y = 280 radius = 200 if len(topics) < 3 or not series: return _empty_svg(width, height, "Choose at least 3 axes and 1 legislator") axis_count = len(topics) def point_for(index: int, score: float) -> tuple[float, float]: angle = -pi / 2 + (2 * pi * index / axis_count) distance = radius * max(0, min(score, 100)) / 100 return center_x + cos(angle) * distance, center_y + sin(angle) * distance def ring_points(score: float) -> str: return " ".join( f"{point_for(index, score)[0]:.2f},{point_for(index, score)[1]:.2f}" for index in range(axis_count) ) parts: list[str] = [ f'") return "".join(parts) def _point_marker(*, marker: str, x: float, y: float, color: str, label: str) -> str: title = f"