From eb211a2e5d37be7c2cbaa5359999b010306a2fe5 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Sun, 14 Jun 2026 11:47:19 +0200 Subject: [PATCH] feat(cockpit): dependency-free inline-SVG nav sparkline Co-Authored-By: Claude Opus 4.8 (1M context) --- src/fxhnt/adapters/web/__init__.py | 0 src/fxhnt/adapters/web/charts.py | 23 +++++++++++++++++++++++ tests/unit/test_charts.py | 20 ++++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 src/fxhnt/adapters/web/__init__.py create mode 100644 src/fxhnt/adapters/web/charts.py create mode 100644 tests/unit/test_charts.py diff --git a/src/fxhnt/adapters/web/__init__.py b/src/fxhnt/adapters/web/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/fxhnt/adapters/web/charts.py b/src/fxhnt/adapters/web/charts.py new file mode 100644 index 0000000..b83739b --- /dev/null +++ b/src/fxhnt/adapters/web/charts.py @@ -0,0 +1,23 @@ +"""Dependency-free inline-SVG charts. Server-rendered so the cockpit needs no JS charting library — the SVG +string is embedded directly in the HTML. nav_sparkline maps a NAV series to a polyline scaled to the box.""" +from __future__ import annotations + + +def nav_sparkline(nav: list[float], *, width: int = 120, height: int = 28, + color: str = "#3fb950") -> str: + box = f'' + if not nav: + return box + "" + lo, hi = min(nav), max(nav) + span = (hi - lo) or 1.0 + n = len(nav) + step = width / (n - 1) if n > 1 else 0.0 + pts = [] + for i, v in enumerate(nav): + x = i * step + y = height - (v - lo) / span * (height - 2) - 1 # 1px padding, y inverted + pts.append(f"{x:.1f},{y:.1f}") + stroke = color if nav[-1] >= nav[0] else "#f85149" + return (box + f'') diff --git a/tests/unit/test_charts.py b/tests/unit/test_charts.py new file mode 100644 index 0000000..a693d0c --- /dev/null +++ b/tests/unit/test_charts.py @@ -0,0 +1,20 @@ +"""nav_sparkline renders a self-contained inline SVG polyline from a NAV series (no JS, no external libs).""" +from __future__ import annotations + +from fxhnt.adapters.web.charts import nav_sparkline + + +def test_sparkline_is_svg_with_points() -> None: + svg = nav_sparkline([1.0, 1.01, 1.005, 1.02], width=100, height=20) + assert svg.startswith("") + assert " None: + svg = nav_sparkline([1.0, 1.0, 1.0], width=100, height=20) + assert "