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 numfmt::Precision;
use plotters::{
    backend::DrawingBackend,
    chart::{ChartBuilder, ChartContext},
    coord::{types::RangedCoordf64, Shift},
    drawing::DrawingArea,
    prelude::{Cartesian2d, WHITE},
};

use crate::{drawing_coords::DrawingCoords, plot_options::PlotOptions};

pub fn chart<'a, DB: DrawingBackend>(
    root: &'a DrawingArea<DB, Shift>,
    options: &PlotOptions,
    drawing_coords: &DrawingCoords,
    x_axis_label: &str,
    y_axis_label: &str,
) -> ChartContext<'a, DB, Cartesian2d<RangedCoordf64, RangedCoordf64>> {
    let mut builder = ChartBuilder::on(&root);
    builder
        .x_label_area_size((drawing_coords.chart_height * options.graph_label_bottom) as i32)
        .margin_top(
            (if options.title.is_empty() {
                0.0
            } else {
                drawing_coords.chart_height * options.graph_title_space_top
            } + (drawing_coords.chart_height * options.graph_space_top)) as i32,
        )
        .y_label_area_size((drawing_coords.chart_width * options.graph_label_left) as i32)
        .margin_right((drawing_coords.chart_width * options.graph_space_right) as i32);

    let mut chart_context = builder
        .build_cartesian_2d(
            (drawing_coords.x_min - drawing_coords.x_space)
                ..(drawing_coords.x_max + drawing_coords.x_space),
            (drawing_coords.y_min - drawing_coords.y_space)
                ..(drawing_coords.y_max + drawing_coords.y_space),
        )
        .unwrap();

    let mut mesh = chart_context.configure_mesh();
    mesh.x_desc(x_axis_label)
        .y_desc(y_axis_label)
        .axis_desc_style((
            options.font.as_str(),
            (drawing_coords.chart_width * drawing_coords.chart_height).sqrt() * options.axis_scalar,
        ))
        .x_label_style((
            options.font.as_str(),
            (drawing_coords.chart_width * drawing_coords.chart_height).sqrt()
                * options.x_label_scalar,
        ))
        .y_label_style((
            options.font.as_str(),
            (drawing_coords.chart_width * drawing_coords.chart_height).sqrt()
                * options.y_label_scalar,
        ))
        .axis_style(WHITE)
        .bold_line_style(WHITE)
        .light_line_style(WHITE)
        .max_light_lines(2);

    let x_steps = (drawing_coords.x_max - drawing_coords.x_min) / 11.0;

    let x_log_func = |&x: &f64| {
        if x.abs() < x_steps / 100.0 {
            "0.0".to_string()
        } else {
            let number_formatter = numfmt::Formatter::new().precision(Precision::Significance(3));
            let mut ret = number_formatter.clone().fmt2(10.0_f64.powf(x)).to_string();
            if ret.len() > 5 {
                ret = format!("{:e}", 10.0_f64.powf(x));
            }
            ret
        }
    };

    let x_func = |&x: &f64| {
        if x.abs() < x_steps / 100.0 {
            "0.0".to_string()
        } else {
            let number_formatter = numfmt::Formatter::new().precision(Precision::Significance(3));
            let mut ret = number_formatter.clone().fmt2(x).to_string();
            if ret.len() > 5 {
                ret = format!("{x:e}");
            }
            ret
        }
    };

    if options.x_log {
        mesh.x_label_formatter(&x_log_func);
    } else {
        mesh.x_label_formatter(&x_func);
    }

    let y_steps = (drawing_coords.y_max - drawing_coords.y_min) / 11.0;

    let y_log_func = |&y: &f64| {
        if y.abs() < y_steps / 100.0 {
            "0.0".to_string()
        } else {
            let number_formatter = numfmt::Formatter::new().precision(Precision::Significance(3));
            let mut ret = number_formatter.clone().fmt2(10.0_f64.powf(y)).to_string();
            if ret.len() > 5 {
                ret = format!("{:e}", 10.0_f64.powf(y));
            }
            ret
        }
    };

    let y_func = |&y: &f64| {
        if y.abs() < y_steps / 100.0 {
            "0.0".to_string()
        } else {
            let number_formatter = numfmt::Formatter::new().precision(Precision::Significance(3));
            let mut ret = number_formatter.clone().fmt2(y).to_string();
            if ret.len() > 5 {
                ret = format!("{y:e}");
            }
            ret
        }
    };

    if options.y_log {
        mesh.y_label_formatter(&y_log_func);
    } else {
        mesh.y_label_formatter(&y_func);
    }

    mesh.draw().unwrap();

    chart_context
}