2,218 bytes · the Python implementation · view raw
import math
from typing import Sequence
from .charts_scale_types import NiceDomain
def nice_domain(domain: Sequence[float], count: int) -> NiceDomain:
"""Widen a domain to multiples of a round tick step chosen for about count ticks. Widening can change the best step, so it repeats until the step settles, as d3's nice() does. """if len(domain) != 2:
raise ValueError(f"domain must have exactly 2 values, [from, to]; got {len(domain)}")
if isinstance(count, bool) ornot isinstance(count, int) or count < 1:
raise ValueError(f"count must be a whole number of at least 1, got {count}")
reversed_ = domain[1] < domain[0]
lo = float(domain[1] if reversed_ else domain[0])
hi = float(domain[0] if reversed_ else domain[1])
if lo == hi:
return NiceDomain(domain=[float(domain[0]), float(domain[1])], step=0.0)
step = 0.0for _ in range(10):
nxt = _tick_step(lo, hi, count)
if nxt == step:
break
step = nxt
if step >= 1:
lo = math.floor(lo / step) * step
hi = math.ceil(hi / step) * step
else:
# A fractional step divides badly (0.3 / 0.1 is 2.9999999999999996),# so multiply by its whole-number inverse instead.
inverse = math.floor(1 / step + 0.5)
lo = math.floor(lo * inverse) / inverse
hi = math.ceil(hi * inverse) / inverse
ends = [_round6(hi), _round6(lo)] if reversed_ else [_round6(lo), _round6(hi)]
return NiceDomain(domain=ends, step=_round6(step))
def _tick_step(lo: float, hi: float, count: int) -> float:
raw = (hi - lo) / count
# Powers of ten by repeated multiplication rather than log10, whose last# digit is not guaranteed to agree between languages.
power = 1.0while power * 10 <= raw:
power *= 10while power > raw:
power /= 10
error = raw / power
if error >= math.sqrt(50):
factor = 10elif error >= math.sqrt(10):
factor = 5elif error >= math.sqrt(2):
factor = 2else:
factor = 1return factor * power
def _round6(x: float) -> float:
return math.floor(x * 1e6 + 0.5) / 1e6