import math from typing import Optional, Sequence from .charts_extent import extent from .charts_scale_linear_scale import linear_scale from .charts_shape_line_path import line_path from .charts_shape_types import Point from .charts_sparkline_types import SparkPoint, Sparkline from .math_round_float import round_float def _check_size(value: float, what: str) -> None: if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value) or value < 0: raise ValueError("%s must be a finite number of zero or more, received %r" % (what, value)) def sparkline(values: Sequence[Optional[float]], width: float, height: float, padding: float) -> Sparkline: """A sparkline in a box; see the README for flat and single-value series.""" _check_size(width, "width") _check_size(height, "height") _check_size(padding, "padding") if width - 2 * padding <= 0 or height - 2 * padding <= 0: raise ValueError( "the box is too small for its padding: %r by %r with %r on each side" % (width, height, padding) ) rng = extent(values) if rng is None: return Sparkline(path="", end=None) lo, hi = rng n = len(values) points = [] for i, v in enumerate(values): x = width - padding if n == 1 else linear_scale([0, n - 1], [padding, width - padding], i, False) if v is None: points.append(Point(x=x, y=None)) continue y = height / 2 if lo == hi else linear_scale([lo, hi], [height - padding, padding], v, False) points.append(Point(x=x, y=y)) end = None for p in points: if p.y is not None: end = SparkPoint(x=round_float(p.x, 2), y=round_float(p.y, 2)) return Sparkline(path=line_path(points), end=end)