Functional Weave
Code in TypeScript

charts.ticks@1.0.0

impl/rust/tick_step.rs

2,109 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one

/// 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.
///
/// # Panics
/// Panics if start or stop is not finite or `count` is less than 1.
pub fn tick_step(start: f64, stop: f64, count: i64) -> f64 {
    let (mul, div) = tick_spec(start, stop, count);
    mul / div
}

// Exported for nice_ticks, which needs the step as a multiplier or a divisor.

/// The step as (multiplier, divisor), one of which is 1; (0, 1) when start
/// equals stop.
pub fn tick_spec(start: f64, stop: f64, count: i64) -> (f64, f64) {
    if !start.is_finite() || !stop.is_finite() {
        panic!("start and stop must be finite numbers; got {} and {}", start, stop);
    }
    if count < 1 {
        panic!("count must be a whole number of at least 1, got {}", count);
    }
    let raw = (stop - start).abs() / count as f64;
    if raw == 0.0 {
        return (0.0, 1.0);
    }
    if raw >= 1.0 {
        let mut power = 1.0;
        while power * 10.0 <= raw {
            power *= 10.0;
        }
        return (nice_factor(raw / power) * power, 1.0);
    }
    let mut inverse = 1.0;
    while raw * inverse < 1.0 {
        inverse *= 10.0;
    }
    let factor = nice_factor(raw * inverse);
    if factor == inverse {
        (1.0, 1.0)
    } else {
        (1.0, inverse / factor)
    }
}

fn nice_factor(error: f64) -> f64 {
    if error >= 50f64.sqrt() {
        10.0
    } else if error >= 10f64.sqrt() {
        5.0
    } else if error >= 2f64.sqrt() {
        2.0
    } else {
        1.0
    }
}

pub fn fune_vector(args: &[Value]) -> Value {
    // Refuse what the typed signature cannot hold, with the wording TypeScript
    // and Python use, rather than let the conversion below quietly change it.
    if matches!(args[2], Value::Float(f) if f.fract() != 0.0) {
        panic!("count must be a whole number of at least 1");
    }
    Value::Float(tick_step(args[0].as_f64(), args[1].as_f64(), args[2].as_i64()))
}