use super::funejson::Value; use super::charts_scale_linear_scale::{floats, pair}; use super::math_round_float::round_float; /// The band a category occupies, as d3.scaleBand lays it out (without /// rounding to whole pixels). A reversed range reverses the order of the /// bands, not their direction, so `start` is always the lower coordinate. /// /// # Panics /// Panics on an empty or repeating domain, a value not in it, a range without /// two values, or padding or align out of bounds. pub fn band_scale(domain: &[String], range: &[f64], value: &str, padding_inner: f64, padding_outer: f64, align: f64) -> Band { if !(padding_inner >= 0.0 && padding_inner <= 1.0) { panic!("paddingInner must be between 0 and 1; got {}", padding_inner); } check_outer(padding_outer, "paddingOuter"); check_align(align); let (start, width) = band_position(domain, range, value, padding_inner, padding_outer, align); Band { start: round_float(start, 6), center: round_float(start + width / 2.0, 6), width: round_float(width, 6), } } // Exported for point_scale, which is a band scale whose bands have no width. pub fn check_outer(padding: f64, what: &str) { if !(padding >= 0.0) { panic!("{} must be 0 or more; got {}", what, padding); } } pub fn check_align(align: f64) { if !(align >= 0.0 && align <= 1.0) { panic!("align must be between 0 and 1; got {}", align); } } /// The unrounded lower coordinate of value's band, and the band width. pub fn band_position(domain: &[String], range: &[f64], value: &str, padding_inner: f64, padding_outer: f64, align: f64) -> (f64, f64) { let (r0, r1) = pair(range, "range"); let n = domain.len(); if n == 0 { panic!("domain must not be empty"); } let mut index: Option = None; for i in 0..n { for j in 0..i { if domain[j] == domain[i] { panic!("domain has a repeated value: \"{}\"", domain[i]); } } if domain[i] == value { index = Some(i); } } let index = match index { Some(i) => i, None => panic!("value is not in the domain: \"{}\"", value), }; let reverse = r1 < r0; let mut start = if reverse { r1 } else { r0 }; let stop = if reverse { r0 } else { r1 }; let nf = n as f64; let step = (stop - start) / (nf - padding_inner + padding_outer * 2.0).max(1.0); start += (stop - start - step * (nf - padding_inner)) * align; let slot = if reverse { n - 1 - index } else { index }; (start + step * slot as f64, step * (1.0 - padding_inner)) } pub fn band_to_value(band: &Band) -> Value { Value::obj(vec![ ("start", Value::Float(band.start)), ("center", Value::Float(band.center)), ("width", Value::Float(band.width)), ]) } /// A JSON list of strings, for the vector adapters. pub fn band_strings(value: &Value) -> Vec { value.as_arr().iter().map(|v| v.as_str().to_string()).collect() } pub fn fune_vector(args: &[Value]) -> Value { band_to_value(&band_scale( &band_strings(&args[0]), &floats(&args[1]), args[2].as_str(), args[3].as_f64(), args[4].as_f64(), args[5].as_f64(), )) }