import math from typing import Tuple def tick_step(start: float, stop: float, count: int) -> float: """The tick step for about count ticks: 1, 2 or 5 times a power of ten, as d3-array's tickIncrement chooses it, found without log10 and returned as one correctly rounded division so 0.2 is exactly the double 0.2.""" mul, div = tick_spec(start, stop, count) return mul / div # Exported for nice_ticks, which needs the step as a multiplier or a divisor. def tick_spec(start: float, stop: float, count: int) -> Tuple[float, float]: """The step as (multiplier, divisor), one of which is 1; (0, 1) when start equals stop.""" for v in (start, stop): if isinstance(v, bool) or not isinstance(v, (int, float)) or not math.isfinite(v): raise ValueError(f"start and stop must be finite numbers; got {start} and {stop}") if isinstance(count, bool) or not isinstance(count, int) or count < 1: raise ValueError(f"count must be a whole number of at least 1, got {count}") raw = abs(float(stop) - float(start)) / count if raw == 0: return 0.0, 1.0 if raw >= 1: power = 1.0 while power * 10 <= raw: power *= 10 return _nice_factor(raw / power) * power, 1.0 inverse = 1.0 while raw * inverse < 1: inverse *= 10 factor = _nice_factor(raw * inverse) return (1.0, 1.0) if factor == inverse else (1.0, inverse / factor) def _nice_factor(error: float) -> float: if error >= math.sqrt(50): return 10.0 if error >= math.sqrt(10): return 5.0 if error >= math.sqrt(2): return 2.0 return 1.0