""" radar_svg.py — генерация SVG паутинной диаграммы для сохранения на устройство. Не требует сторонних зависимостей (чистый Python). SVG содержит корректные шрифты и цвета, может быть открыт/сохранён в браузере. """ import math import base64 def generate_radar_svg(items: list[dict], max_val: float = 14.0, size: int = 520) -> str: """ Возвращает SVG-строку паутинной диаграммы. items — список осей (dimension_name, dimension_color, value) max_val — максимум шкалы size — размер холста в пикселях """ n = len(items) if n < 3: return f'Нет данных' cx = cy = size / 2 R = size * 0.27 # радиус внешнего кольца lbl_r = R + size * 0.12 # расстояние до подписей от центра safe_max = max_val if max_val > 0 else 1.0 def axis_xy(i: int, frac: float = 1.0) -> tuple[float, float]: a = math.radians(-90.0 + 360.0 * i / n) return cx + R * frac * math.cos(a), cy + R * frac * math.sin(a) def poly_pts(pts: list[tuple]) -> str: return " ".join(f"{x:.2f},{y:.2f}" for x, y in pts) lines: list[str] = [ f'', # фон f'', ] # ── концентрические кольца (сетка) ────────────────────────────────────── for lvl in range(1, 6): pts = [axis_xy(i, lvl / 5) for i in range(n)] lines.append( f'' ) # ── оси ───────────────────────────────────────────────────────────────── for i in range(n): x, y = axis_xy(i) lines.append( f'' ) # ── полигон данных ─────────────────────────────────────────────────────── data_pts = [axis_xy(i, min(it["value"] / safe_max, 1.0)) for i, it in enumerate(items)] lines.append( f'' ) # ── точки на вершинах ──────────────────────────────────────────────────── for x, y in data_pts: lines.append(f'') # ── подписи осей ───────────────────────────────────────────────────────── for i, item in enumerate(items): a = math.radians(-90.0 + 360.0 * i / n) lx = cx + lbl_r * math.cos(a) ly = cy + lbl_r * math.sin(a) col = item.get("dimension_color") or "#F4EFFA" name = _xml_escape(item["dimension_name"]) val = int(item["value"]) # название типа (жирный, цветной) lines.append( f'{name}' ) # значение (белый) lines.append( f'{val}' ) lines.append("") return "\n".join(lines) def _xml_escape(s: str) -> str: return s.replace("&", "&").replace("<", "<").replace(">", ">") def get_radar_data_uri(items: list[dict], max_val: float = 14.0) -> str: """ Возвращает data URI вида «data:image/svg+xml;base64,…». Передайте в page.launch_url(...) чтобы открыть/сохранить изображение. """ svg = generate_radar_svg(items, max_val) b64 = base64.b64encode(svg.encode("utf-8")).decode() return f"data:image/svg+xml;base64,{b64}"