import math from typing import List, Sequence from .charts_stack_types import StackedValue, StackOffset from .math_round_float import round_float def stack(series: Sequence[Sequence[float]], offset: StackOffset) -> List[List[StackedValue]]: """Stack series position by position, as d3.stack does with the series in the given order: stackOffsetNone (zero), stackOffsetExpand and stackOffsetDiverging. Running totals stay unrounded; only the returned edges are rounded.""" if offset not in ("zero", "expand", "diverging"): raise ValueError("stack offset must be zero, expand or diverging, received %r" % (offset,)) n = len(series) if n == 0: return [] m = len(series[0]) for s in series: if len(s) != m: raise ValueError("every series must have the same number of values") for v in s: if isinstance(v, bool) or not isinstance(v, (int, float)) or not math.isfinite(v): raise TypeError("stack values must be finite numbers, received %r" % (v,)) if offset == "expand" and v < 0: raise ValueError("expand needs values of zero or more, received %r" % (v,)) lower = [[0.0] * m for _ in range(n)] upper = [[0.0] * m for _ in range(n)] for j in range(m): if offset == "diverging": up = 0.0 down = 0.0 for i in range(n): v = float(series[i][j]) if v > 0: lower[i][j] = up up += v upper[i][j] = up elif v < 0: upper[i][j] = down down += v lower[i][j] = down else: lower[i][j] = 0.0 upper[i][j] = 0.0 else: total = 0.0 if offset == "expand": for i in range(n): total += float(series[i][j]) top = 0.0 for i in range(n): v = float(series[i][j]) if offset == "expand": v = v / total if total > 0 else 0.0 lower[i][j] = top top += v upper[i][j] = top return [ [StackedValue(y0=round_float(lower[i][j], 6), y1=round_float(upper[i][j], 6)) for j in range(m)] for i in range(n) ]