evolutionary 0.1.1

A fully extensible Rust framework for using paralyzed genetic algorithms to solve problems.
Documentation
use plotters::backend::BitMapBackend;
use plotters::chart::ChartBuilder;
use plotters::data::Quartiles;
use plotters::element::{Boxplot, PathElement};
use plotters::prelude::*;

fn normalize(values: &Vec<f64>, max: f64) -> Vec<f64> {
    let min = 0.0;
    values.iter().map(|&v| (v - min) / (max - min)).collect()
}

pub fn plot_chart(
    best_fitness: &Vec<f64>,
    average_fitness: &Vec<f64>,
    path: &String,
    test_name: &String,
) -> Result<(), Box<dyn std::error::Error>> {
    let max = *average_fitness
        .iter()
        .chain(best_fitness)
        .max_by(|a, b| a.partial_cmp(b).unwrap())
        .unwrap_or(&1.0);

    let normalized_avg = normalize(&average_fitness, max);
    let normalized_best = normalize(&best_fitness, max);

    let min = normalized_avg
        .iter()
        .chain(&normalized_best)
        .min_by(|a, b| a.partial_cmp(b).unwrap())
        .unwrap_or(&0.0);

    let y_axis_start = if *min > 0.5 { min } else { min };

    let y_axis_start = y_axis_start - (y_axis_start * 0.1);

    let root = BitMapBackend::new(&path, (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption(format!("{}", test_name), ("sans-serif", 40))
        .margin(10)
        .x_label_area_size(30)
        .y_label_area_size(40)
        .build_cartesian_2d(0f64..normalized_best.len() as f64, (y_axis_start)..1.05)?;

    chart.configure_mesh().draw()?;

    chart
        .draw_series(LineSeries::new(
            normalized_best
                .iter()
                .enumerate()
                .map(|(x, y)| (x as f64, *y)),
            &RED,
        ))?
        .label("Best Fitness")
        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &RED));

    chart
        .draw_series(LineSeries::new(
            normalized_avg
                .iter()
                .enumerate()
                .map(|(x, y)| (x as f64, *y)),
            &BLUE,
        ))?
        .label("Average Fitness")
        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &BLUE));

    chart
        .configure_series_labels()
        .background_style(&WHITE.mix(0.8))
        .border_style(&BLACK)
        .draw()?;

    Ok(())
}

pub fn plot_boxplot(
    quartiles: &Vec<Quartiles>,
    labels: &Vec<String>,
) -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("boxplot.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let min = quartiles
        .iter()
        .map(|q| q.values()[0])
        .min_by(|a, b| a.partial_cmp(b).unwrap())
        .unwrap();
    let max = quartiles
        .iter()
        .map(|q| q.values()[4])
        .max_by(|a, b| a.partial_cmp(b).unwrap())
        .unwrap();

    let mut chart = ChartBuilder::on(&root)
        .caption("128 Queens Score", ("sans-serif", 40))
        .margin(10)
        .x_label_area_size(30)
        .y_label_area_size(40)
        .build_cartesian_2d(labels.into_segmented(), min..max)?;

    chart.configure_mesh().draw()?;

    for (quartile, label) in quartiles.iter().zip(labels.iter()) {
        chart.draw_series(vec![Boxplot::new_vertical(
            SegmentValue::CenterOf(label),
            quartile,
        )])?;
    }

    Ok(())
}