from typing import List, Sequence, Tuple from .charts_shape_types import Point from .text_format_decimal import format_decimal def line_path(points: Sequence[Point]) -> str: """A polyline as SVG path data, in d3.line's format ("M10,20L30,40"). A null y ends the current subpath; a run of one point is "Mx,yZ", as d3 closes it so a round line cap still shows a dot. """ out = "" for run in point_runs(points): for i, (x, y) in enumerate(run): out += ("M" if i == 0 else "L") + svg_pair(x, y) if len(run) == 1: out += "Z" return out # Shared with the other path functions of this group. def svg_pair(x: float, y: float) -> str: """"x,y" with each number rounded to 2 places, trailing zeros dropped.""" return format_decimal(x, 2, True, "") + "," + format_decimal(y, 2, True, "") def point_runs(points: Sequence[Point]) -> List[List[Tuple[float, float]]]: """The unbroken runs of a series: a point with a null y separates them.""" runs: List[List[Tuple[float, float]]] = [] current: List[Tuple[float, float]] = [] for p in points: if p.y is None: if current: runs.append(current) current = [] else: # float() so integer inputs take the same binary64 path as the # other languages. current.append((float(p.x), float(p.y))) if current: runs.append(current) return runs