from typing import Sequence, Tuple from .charts_scale_linear_scale import pair from .charts_scale_types import Band from .math_round_float import round_float def band_scale( domain: Sequence[str], range: Sequence[float], value: str, padding_inner: float, padding_outer: float, align: float, ) -> Band: """The band a category occupies, as d3.scaleBand lays it out (without rounding to whole pixels). A reversed range reverses the order of the bands, not their direction, so start is always the lower coordinate.""" if not (0 <= padding_inner <= 1): raise ValueError(f"paddingInner must be between 0 and 1; got {padding_inner}") check_outer(padding_outer, "paddingOuter") check_align(align) start, width = band_position(domain, range, value, padding_inner, padding_outer, align) return Band(start=round_float(start, 6), center=round_float(start + width / 2, 6), width=round_float(width, 6)) # Exported for point_scale, which is a band scale whose bands have no width. def check_outer(padding: float, what: str) -> None: if not (padding >= 0): raise ValueError(f"{what} must be 0 or more; got {padding}") def check_align(align: float) -> None: if not (0 <= align <= 1): raise ValueError(f"align must be between 0 and 1; got {align}") def band_position( domain: Sequence[str], extent: Sequence[float], value: str, padding_inner: float, padding_outer: float, align: float, ) -> Tuple[float, float]: """The unrounded lower coordinate of value's band, and the band width.""" r0, r1 = pair(extent, "range") n = len(domain) if n == 0: raise ValueError("domain must not be empty") index = -1 for i in range(n): for j in range(i): if domain[j] == domain[i]: raise ValueError(f'domain has a repeated value: "{domain[i]}"') if domain[i] == value: index = i if index < 0: raise ValueError(f'value is not in the domain: "{value}"') reverse = r1 < r0 start = r1 if reverse else r0 stop = r0 if reverse else r1 step = (stop - start) / max(1.0, n - float(padding_inner) + float(padding_outer) * 2) start += (stop - start - step * (n - float(padding_inner))) * float(align) slot = n - 1 - index if reverse else index return start + step * slot, step * (1 - float(padding_inner))