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 plotters::{backend::DrawingBackend, coord::Shift, drawing::DrawingArea};

use crate::plot_options::PlotOptions;

#[derive(Copy, Clone, Debug)]
pub struct DrawingCoords {
    pub chart_left: f64,
    pub chart_right: f64,
    pub chart_top: f64,
    pub chart_bottom: f64,
    pub chart_width: f64,
    pub chart_height: f64,
    pub x_min: f64,
    pub x_max: f64,
    pub x_space: f64,
    pub y_min: f64,
    pub y_max: f64,
    pub y_space: f64,
}

pub fn drawing_coords<
    'a,
    'b,
    B: DrawingBackend,
    I1: IntoIterator<Item = &'a f64>,
    I2: IntoIterator<Item = &'b f64>,
>(
    x_data: I1,
    y_data: I2,
    root: &DrawingArea<B, Shift>,
    options: &PlotOptions,
) -> DrawingCoords {
    let x = x_data.into_iter().cloned().collect::<Vec<_>>();
    let x_min = options
        .x_min
        .map(|x| if options.x_log { x.abs().log10() } else { x })
        .unwrap_or(
            x.iter()
                .filter(|x| x.is_finite())
                .cloned()
                .min_by(|f1, f2| f1.partial_cmp(f2).unwrap())
                .unwrap_or(0.0),
        );
    let x_max = options
        .x_max
        .map(|x| if options.x_log { x.abs().log10() } else { x })
        .unwrap_or(
            x.iter()
                .filter(|x| x.is_finite())
                .cloned()
                .max_by(|f1, f2| f1.partial_cmp(f2).unwrap())
                .unwrap_or(0.0),
        );
    let x_space = (x_max - x_min).abs() * options.x_plot_scalar;

    let y = y_data.into_iter().cloned().collect::<Vec<_>>();
    let y_min = options
        .y_min
        .map(|y| if options.y_log { y.abs().log10() } else { y })
        .unwrap_or(
            y.iter()
                .filter(|y| y.is_finite())
                .cloned()
                .min_by(|f1, f2| f1.partial_cmp(f2).unwrap())
                .unwrap_or(0.0),
        );
    let y_max = options
        .y_max
        .map(|y| if options.y_log { y.abs().log10() } else { y })
        .unwrap_or(
            y.iter()
                .filter(|y| y.is_finite())
                .cloned()
                .max_by(|f1, f2| f1.partial_cmp(f2).unwrap())
                .unwrap_or(0.0),
        );
    let y_space = (y_max - y_min).abs() * options.y_plot_scalar;

    let (root_width, root_height) = root.dim_in_pixel();

    let top = if options.title.is_empty() {
        0.0
    } else {
        (root_height as f64) * options.graph_title_space_top
    } + ((root_height as f64) * options.graph_space_top);
    let bottom = (root_height as f64) - ((root_height as f64) * options.graph_label_bottom);
    let left = (root_width as f64) * options.graph_label_left;
    let right = (root_width as f64) - ((root_width as f64) * options.graph_space_right);

    DrawingCoords {
        chart_top: top,
        chart_left: left,
        chart_right: right,
        chart_bottom: bottom,
        chart_height: root_height as f64,
        chart_width: root_width as f64,
        x_min,
        x_max,
        x_space,
        y_min,
        y_max,
        y_space,
    }
}