Functional Weave
Code in Python

charts.ticks@1.0.0

impl/python/tick_step.py

1,653 bytes · the Python implementation · view raw

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