strafe-plot 0.1.1

Statistical plotting for Rust statistics based on R
// "Whatever you do, work at it with all your heart, as working for the Lord,
// not for human masters, since you know that you will receive an inheritance
// from the Lord as a reward. It is the Lord Christ you are serving."
// (Col 3:23-24)

use std::fmt::Write;

use r2rs_nmath::traits::DPQ;

/// # Stem-and-Leaf Plots
///
/// ## Description:
///
/// ‘stem’ produces a stem-and-leaf plot of the values in ‘x’.  The
/// parameter ‘scale’ can be used to expand the scale of the plot.  A
/// value of ‘scale = 2’ will cause the plot to be roughly twice as
/// long as the default.
///
/// ## Usage:
///
/// stem(x, scale = 1, width = 80, atom = 1e-08)
///
/// ## Arguments:
///
/// * x: a numeric vector.
/// * scale: This controls the plot length.
/// * width: The desired width of plot.
/// * atom: a tolerance.
///
/// ## Details:
///
/// Infinite and missing values in ‘x’ are discarded.
///
/// ## References:
///
/// Becker, R. A., Chambers, J. M. and Wilks, A. R. (1988) _The New S
/// Language_.  Wadsworth & Brooks/Cole.
///
/// ## Examples:
///
/// stem(islands)
/// stem(log10(islands))
pub fn stem_leaf_plot(x: &[f64]) -> String {
    let atom = 1e-8;
    let scale = 1.0;
    let width = 80;

    let mut x = x
        .iter()
        .filter(|x| x.is_finite())
        .cloned()
        .collect::<Vec<_>>();
    x.sort_by(|x1, x2| x1.partial_cmp(x2).unwrap());
    let n = x.len();

    let mut mu = 10;
    let r;
    let mut c;
    if x[n - 1] > x[0] {
        r = atom + (x[n - 1] - x[0]) / scale;
        // this needs to be exact: exp10 in glibc is not accurate
        c = 10.0_f64.pow_di((1.0 - r.log10().floor()) as isize);
        let mm = 0.max((r * c / 25.0) as isize).min(2);
        let k = 3 * mm + 2 - 150 / (n as isize + 50);
        if (k - 1) * (k - 2) * (k - 5) == 0 {
            c *= 10.;
        }
        /* need to ensure that x[i]*c does not integer overflow */
        let mut x1 = x[0].abs();
        let x2 = x[n - 1].abs();
        if x2 > x1 {
            x1 = x2;
        }
        while x1 * c > usize::MAX as f64 {
            c /= 10.0;
        }
        if k * (k - 4) * (k - 8) == 0 {
            mu = 5;
        }
        if (k - 1) * (k - 5) * (k - 6) == 0 {
            mu = 20;
        }
    } else {
        r = atom + x[0].abs() / scale;
        c = 10.0_f64.pow_di((1.0 - r.log10().floor()) as isize);
    }

    // Find the print width of the stem.

    let mut lo = (x[0] * c / mu as f64).floor() as isize * mu;
    let mut hi = (x[n - 1] * c / mu as f64).floor() as isize * mu;
    let ldigits = if lo < 0 {
        (-lo as f64).log10().floor() as usize + 1
    } else {
        0
    };
    let hdigits = if hi > 0 {
        (hi as f64).log10().floor() as usize
    } else {
        0
    };
    let ndigits = if ldigits < hdigits { hdigits } else { ldigits };

    // Starting cell

    if lo < 0 && (x[0] * c).floor() as isize == lo {
        lo = lo - mu;
    }
    hi = lo + mu;
    if (x[0] * c + 0.5).floor() as isize > hi {
        lo = hi;
        hi = lo + mu;
    }

    let mut ret = String::new();

    // Write out the info about the decimal place

    let pdigits = 1 - (c.log10() + 0.5).floor() as isize;

    if pdigits == 0 {
        writeln!(ret, "The decimal point is at the |\n").unwrap();
    } else {
        writeln!(
            ret,
            "The decimal point is {} digit(s) to the {} of the |\n",
            pdigits.abs(),
            if pdigits > 0 { "right" } else { "left" }
        )
        .unwrap();
    }

    let stem_print = |ret: &mut String, close: isize, dist: isize, ndigits: usize| {
        if close / 10 == 0 && dist < 0 {
            write!(ret, "{:width$} | ", "-0", width = ndigits).unwrap();
        } else {
            write!(ret, "{:width$} | ", close / 10, width = ndigits).unwrap();
        }
    };

    let mut i = 0;
    loop {
        if lo < 0 {
            stem_print(&mut ret, hi, lo, ndigits);
        } else {
            stem_print(&mut ret, lo, hi, ndigits);
        }
        let mut j = 0;
        loop {
            let xi = if x[i] < 0.0 {
                (x[i] * c - 0.5) as isize
            } else {
                (x[i] * c + 0.5) as isize
            };

            if (hi == 0 && x[i] >= 0.0) || (lo < 0 && xi > hi) || (lo >= 0 && xi >= hi) {
                break;
            }

            j += 1;
            if j <= width - 12 {
                write!(ret, "{}", xi.abs() % 10).unwrap();
            }
            i += 1;
            if !(i < n) {
                break;
            }
        }
        if j > width {
            write!(ret, "+{}", j - width).unwrap();
        }
        writeln!(ret).unwrap();
        if i >= n {
            break;
        }
        hi += mu;
        lo += mu;
    }

    ret
}