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
use super::charts_ticks_tick_step::tick_spec;
use super::math_pow::pow; ← from math.pow ^1.0.0 · built alongside by fune
use super::math_round_float::round_float; ← from math.round-float ^1.0.0 · built alongside by fune
use super::stats_percentile::percentile; ← from stats.percentile ^1.0.0 · built alongside by fune
const MAX_BINS: i64 = 10000;
/// Histogram bins on round edges, as d3.bin lays them out. Each bin holds
/// x0 <= v < x1; the last also holds its right edge. See the README.
///
/// # Panics
/// Panics on an unknown method, a binWidth given for a method that does not
/// take one or missing for fixed-width, a non-finite value, or more than
/// 10,000 bins.
pub fn histogram_bins(values: &[f64], method: &str, bin_width: Option<f64>) -> Vec<Bin> {
match method {
"fixed-width" => match bin_width {
Some(w) if w.is_finite() && w > 0.0 => {}
Some(w) => panic!("fixed-width needs a binWidth greater than 0; got {}", w),
None => panic!("fixed-width needs a binWidth greater than 0; got null"),
},
"sturges" | "freedman-diaconis" => {
if bin_width.is_some() {
panic!("binWidth is only for fixed-width; pass null for {}", method);
}
}
other => panic!("unknown bin method \"{}\"", other),
}
let n = values.len();
if n == 0 {
return Vec::new();
}
let mut lo = values[0];
let mut hi = values[0];
for &v in values {
if !v.is_finite() {
panic!("values must be finite numbers; got {}", v);
}
if v < lo {
lo = v;
}
if v > hi {
hi = v;
}
}
let (mul, div) = if method == "fixed-width" {
(bin_width.unwrap(), 1.0)
} else {
if lo == hi {
return vec![Bin { x0: lo + 0.0, x1: hi + 0.0, count: n as i64 }];
}
let count = if method == "sturges" {
// ceil(log2 n) + 1, by doubling rather than a logarithm.
let (mut bits, mut power) = (0i64, 1usize);
while power < n {
power *= 2;
bits += 1;
}
bits + 1
} else {
// Freedman and Diaconis: bin width 2 IQR n^(-1/3); d3 falls back
// to one bin when the interquartile range is zero.
let iqr = percentile(values, 75.0, "linear", 12) - percentile(values, 25.0, "linear", 12);
let width = 2.0 * iqr * pow(n as f64, -1.0 / 3.0);
let bins = if width > 0.0 { ((hi - lo) / width).ceil() } else { 1.0 };
if bins > MAX_BINS as f64 {
panic!("too many bins: more than {}", MAX_BINS);
}
(bins as i64).max(1)
};
tick_spec(lo, hi, count)
};
// One exact operation on a whole number, cleaned at 12 places.
let edge = |i: i64| round_float(if div > 1.0 { i as f64 / div } else { i as f64 * mul }, 12);
let mut first = (if div > 1.0 { lo * div } else { lo / mul }).floor() as i64;
while edge(first) > lo {
first -= 1;
}
while edge(first + 1) <= lo {
first += 1;
}
let mut last = first + 1;
while edge(last) < hi {
last += 1;
if last - first > MAX_BINS {
panic!("too many bins: more than {}", MAX_BINS);
}
}
let edges: Vec<f64> = (first..=last).map(edge).collect();
let mut counts = vec![0i64; edges.len() - 1];
for &v in values {
let (mut a, mut b) = (0usize, counts.len() - 1);
while a < b {
let mid = (a + b + 1) / 2;
if edges[mid] <= v {
a = mid;
} else {
b = mid - 1;
}
}
counts[a] += 1;
}
counts
.iter()
.enumerate()
.map(|(i, &count)| Bin { x0: edges[i], x1: edges[i + 1], count })
.collect()
}
pub fn bin_to_value(bin: &Bin) -> Value {
Value::obj(vec![("x0", Value::Float(bin.x0)), ("x1", Value::Float(bin.x1)), ("count", Value::Int(bin.count))])
}
pub fn fune_vector(args: &[Value]) -> Value {
let values: Vec<f64> = args[0].as_arr().iter().map(|v| v.as_f64()).collect();
let width = if args[2].is_null() { None } else { Some(args[2].as_f64()) };
Value::Arr(histogram_bins(&values, args[1].as_str(), width).iter().map(bin_to_value).collect())
}