use super::funejson::Value; /// Where `value` lands on the range, as a straight-line map from the domain. /// Outside the domain the line carries on, unless `clamp` stops it at the /// range's ends. /// /// # Panics /// Panics if the domain or range does not have exactly two values, or the /// domain's ends are equal. pub fn linear_scale(domain: &[f64], range: &[f64], value: f64, clamp: bool) -> f64 { let (d0, d1) = pair(domain, "domain"); let (r0, r1) = pair(range, "range"); let mut t = normalise(d0, d1, value, "domain"); if clamp { t = t.max(0.0).min(1.0); } round6(interpolate(r0, r1, t)) } // The helpers below are shared with invert_linear, which is the same line read // the other way; they are not part of the group's contract. pub fn pair(values: &[f64], what: &str) -> (f64, f64) { if values.len() != 2 { panic!("{} must have exactly 2 values, [from, to]; got {}", what, values.len()); } (values[0], values[1]) } pub fn normalise(from: f64, to: f64, value: f64, what: &str) -> f64 { // Equal ends would divide by zero and answer NaN or infinity, which then // draws nothing without saying why. if from == to { panic!("{} has no width: both ends are {}", what, from); } (value - from) / (to - from) } pub fn interpolate(from: f64, to: f64, t: f64) -> f64 { from + t * (to - from) } /// Half up to 6 decimal places, the same way in every language. pub fn round6(x: f64) -> f64 { (x * 1e6 + 0.5).floor() / 1e6 } /// A JSON list of numbers, for the vector adapters. pub fn floats(value: &Value) -> Vec { value.as_arr().iter().map(|v| v.as_f64()).collect() } pub fn fune_vector(args: &[Value]) -> Value { Value::Float(linear_scale( &floats(&args[0]), &floats(&args[1]), args[2].as_f64(), args[3].as_bool(), )) }