feat(cockpit): dependency-free inline-SVG nav sparkline
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
0
src/fxhnt/adapters/web/__init__.py
Normal file
0
src/fxhnt/adapters/web/__init__.py
Normal file
23
src/fxhnt/adapters/web/charts.py
Normal file
23
src/fxhnt/adapters/web/charts.py
Normal file
@@ -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'<svg width="{width}" height="{height}" viewBox="0 0 {width} {height}" '\
|
||||
'xmlns="http://www.w3.org/2000/svg">'
|
||||
if not nav:
|
||||
return box + "</svg>"
|
||||
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'<polyline fill="none" stroke="{stroke}" stroke-width="1.5" '
|
||||
f'points="{" ".join(pts)}" /></svg>')
|
||||
20
tests/unit/test_charts.py
Normal file
20
tests/unit/test_charts.py
Normal file
@@ -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("<svg") and svg.rstrip().endswith("</svg>")
|
||||
assert "<polyline" in svg and "points=" in svg
|
||||
|
||||
|
||||
def test_sparkline_empty_series_is_safe() -> None:
|
||||
svg = nav_sparkline([], width=100, height=20)
|
||||
assert svg.startswith("<svg") and "polyline" not in svg
|
||||
|
||||
|
||||
def test_sparkline_flat_series_does_not_divide_by_zero() -> None:
|
||||
svg = nav_sparkline([1.0, 1.0, 1.0], width=100, height=20)
|
||||
assert "<polyline" in svg # flat line, no exception
|
||||
Reference in New Issue
Block a user