update diogram
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"""
|
||||
RadarChart — переиспользуемый виджет паутинной диаграммы (ft.Stack).
|
||||
RadarChart — переиспользуемый виджет 2D паутинной диаграммы (ft.Stack).
|
||||
Отрицательные значения — красный полигон, положительные — фиолетовый.
|
||||
|
||||
Параметры:
|
||||
items — список осей из API /responses/{id}/radar:
|
||||
@@ -12,13 +13,13 @@ import flet.canvas as cv
|
||||
import designer as ds
|
||||
|
||||
# ── размеры ─────────────────────────────────────────────────────────────────
|
||||
_CS = 260 # размер canvas (×2 = итоговая зона графика)
|
||||
_CS = 260 # размер canvas
|
||||
_CX = _CS // 2
|
||||
_CY = _CS // 2
|
||||
_R = 80 # радиус внешнего кольца
|
||||
_PAD = 95 # отступ вокруг canvas — оставляем место для подписей
|
||||
_LBL_R = _R + 50 # расстояние от центра до центра подписи (в координатах Stack)
|
||||
_W_LBL = 130 # ширина контейнера подписи (достаточно для «Конвенциональный»)
|
||||
_R = 78 # радиус внешнего кольца
|
||||
_PAD = 95 # отступ вокруг canvas под подписи
|
||||
_LBL_R = _R + 52 # расстояние от центра до подписи (в координатах Stack)
|
||||
_W_LBL = 130 # ширина контейнера подписи
|
||||
|
||||
|
||||
def _axis_xy(n: int, i: int, frac: float = 1.0) -> tuple[float, float]:
|
||||
@@ -35,56 +36,67 @@ def _closed_path(pts: list[tuple]) -> list:
|
||||
|
||||
|
||||
class RadarChart(ft.Stack):
|
||||
"""Паутинная диаграмма типов/сфер по данным radar API."""
|
||||
"""2D паутинная диаграмма. Отрицательные=красный, позитивные=синий."""
|
||||
|
||||
def __init__(self, items: list[dict], max_val: float = 14.0):
|
||||
n = len(items)
|
||||
n = len(items)
|
||||
shapes: list = []
|
||||
vals = [it["value"] for it in items]
|
||||
max_abs = max((abs(v) for v in vals), default=1.0) if vals else 1.0
|
||||
if max_val > 0:
|
||||
max_abs = max(max_abs, max_val)
|
||||
max_abs = max(max_abs, 1.0)
|
||||
|
||||
# ── сетка (5 уровней) ────────────────────────────────────────────────
|
||||
# ── Сетка ─────────────────────────────────────────────────────────────
|
||||
for lvl in range(1, 6):
|
||||
ring = [_axis_xy(n, i, lvl / 5) for i in range(n)]
|
||||
ring = [_axis_xy(n, i, lvl / 5) for i in range(n)]
|
||||
alpha = int((0.10 + 0.09 * (lvl / 5)) * 255)
|
||||
shapes.append(cv.Path(
|
||||
_closed_path(ring),
|
||||
paint=ft.Paint(
|
||||
style=ft.PaintingStyle.STROKE,
|
||||
stroke_width=1,
|
||||
color="#30F4EFFA",
|
||||
color=f"#{alpha:02X}F4EFFA",
|
||||
),
|
||||
))
|
||||
|
||||
# ── оси от центра ────────────────────────────────────────────────────
|
||||
# ── Оси от центра ──────────────────────────────────────────────────────
|
||||
for i in range(n):
|
||||
x, y = _axis_xy(n, i)
|
||||
shapes.append(cv.Path(
|
||||
[cv.Path.MoveTo(_CX, _CY), cv.Path.LineTo(x, y)],
|
||||
paint=ft.Paint(stroke_width=1, color="#40F4EFFA"),
|
||||
paint=ft.Paint(stroke_width=1, color="#38F4EFFA"),
|
||||
))
|
||||
|
||||
# ── полигон данных ───────────────────────────────────────────────────
|
||||
safe_max = max_val if max_val > 0 else 1.0
|
||||
data_pts = [
|
||||
_axis_xy(n, i, min(it["value"] / safe_max, 1.0))
|
||||
for i, it in enumerate(items)
|
||||
]
|
||||
shapes.append(cv.Path(
|
||||
_closed_path(data_pts),
|
||||
paint=ft.Paint(style=ft.PaintingStyle.FILL, color="#609B72CF"),
|
||||
))
|
||||
shapes.append(cv.Path(
|
||||
_closed_path(data_pts),
|
||||
paint=ft.Paint(
|
||||
style=ft.PaintingStyle.STROKE,
|
||||
stroke_width=2.5,
|
||||
color="#F4EFFA",
|
||||
),
|
||||
))
|
||||
for px, py in data_pts:
|
||||
shapes.append(cv.Circle(px, py, 4, paint=ft.Paint(color="#F4EFFA")))
|
||||
# ── Полигоны данных ────────────────────────────────────────────────────
|
||||
neg_pts = [_axis_xy(n, i, max(0.0, min(-it["value"] / max_abs, 1.0))) for i, it in enumerate(items)]
|
||||
pos_pts = [_axis_xy(n, i, max(0.0, min( it["value"] / max_abs, 1.0))) for i, it in enumerate(items)]
|
||||
|
||||
# Красный (отрицательные)
|
||||
shapes.append(cv.Path(_closed_path(neg_pts),
|
||||
paint=ft.Paint(style=ft.PaintingStyle.FILL, color="#50CC3030")))
|
||||
shapes.append(cv.Path(_closed_path(neg_pts),
|
||||
paint=ft.Paint(style=ft.PaintingStyle.STROKE, stroke_width=2.5, color="#FF7070")))
|
||||
|
||||
# Фиолетовый (положительные)
|
||||
shapes.append(cv.Path(_closed_path(pos_pts),
|
||||
paint=ft.Paint(style=ft.PaintingStyle.FILL, color="#505030A0")))
|
||||
shapes.append(cv.Path(_closed_path(pos_pts),
|
||||
paint=ft.Paint(style=ft.PaintingStyle.STROKE, stroke_width=2.5, color="#A080FF")))
|
||||
|
||||
# ── Точки на вершинах ──────────────────────────────────────────────────
|
||||
for i, it in enumerate(items):
|
||||
v = it["value"]
|
||||
if abs(v) > 0.05:
|
||||
px, py = _axis_xy(n, i, abs(v) / max_abs)
|
||||
col = "#FF7070" if v < 0 else "#A080FF"
|
||||
shapes.append(cv.Circle(px, py, 4, paint=ft.Paint(color=col)))
|
||||
shapes.append(cv.Circle(px, py, 4,
|
||||
paint=ft.Paint(style=ft.PaintingStyle.STROKE, stroke_width=1.5, color="#F4EFFA")))
|
||||
|
||||
canvas = cv.Canvas(shapes=shapes, width=_CS, height=_CS)
|
||||
|
||||
# ── подписи оси в Stack-координатах ──────────────────────────────────
|
||||
# ── Подписи осей в Stack-координатах ─────────────────────────────────
|
||||
sw = _CS + 2 * _PAD
|
||||
sh = _CS + 2 * _PAD
|
||||
ccx = _PAD + _CX # центр графика в Stack
|
||||
@@ -92,9 +104,9 @@ class RadarChart(ft.Stack):
|
||||
|
||||
labels: list[ft.Control] = []
|
||||
for i, item in enumerate(items):
|
||||
a = math.radians(-90.0 + 360.0 * i / n)
|
||||
lx = ccx + _LBL_R * math.cos(a)
|
||||
ly = ccy + _LBL_R * math.sin(a)
|
||||
a = math.radians(-90.0 + 360.0 * i / n)
|
||||
lx = ccx + _LBL_R * math.cos(a)
|
||||
ly = ccy + _LBL_R * math.sin(a)
|
||||
col = item.get("dimension_color") or ds.colors.primary
|
||||
|
||||
labels.append(ft.Container(
|
||||
@@ -111,7 +123,6 @@ class RadarChart(ft.Stack):
|
||||
color=col,
|
||||
text_align=ft.TextAlign.CENTER,
|
||||
weight=ft.FontWeight.BOLD,
|
||||
# разрешаем перенос только по словам (не по символам)
|
||||
overflow=ft.TextOverflow.FADE,
|
||||
max_lines=2,
|
||||
no_wrap=False,
|
||||
|
||||
Reference in New Issue
Block a user