radiate-ui 1.3.0

A Rust library for genetic algorithms and artificial evolution.
Documentation
use radiate_utils::WindowBuffer;
use ratatui::style::Color;

pub trait LineChart {
    fn values(&self) -> &[(f64, f64)];
    fn min_x(&self) -> f64;
    fn max_x(&self) -> f64;
    fn min_y(&self) -> f64;
    fn max_y(&self) -> f64;
    fn color(&self) -> Color;
    fn title(&self) -> &str;
}

pub struct RollingLineChart {
    title: String,
    min_y: f64,
    max_y: f64,
    values: WindowBuffer<(f64, f64)>,
    color: Color,
    point_count: usize,
}

impl RollingLineChart {
    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            title: "".to_string(),
            min_y: f64::MAX,
            max_y: f64::MIN,
            values: WindowBuffer::with_capacity(capacity),
            color: Color::White,
            point_count: 0,
        }
    }

    pub fn with_title<S: Into<String>>(mut self, title: S) -> Self {
        self.title = title.into();
        self
    }

    pub fn with_color(mut self, color: Color) -> Self {
        self.color = color;
        self
    }

    pub fn title(&self) -> &str {
        &self.title
    }

    pub fn color(&self) -> Color {
        self.color
    }

    pub fn values(&self) -> &[(f64, f64)] {
        self.values.values()
    }

    pub fn min_x(&self) -> f64 {
        self.values().first().map_or(0.0, |v| v.0)
    }

    pub fn max_x(&self) -> f64 {
        self.values().last().map_or(0.0, |v| v.0)
    }

    pub fn min_y(&self) -> f64 {
        self.min_y
    }

    pub fn max_y(&self) -> f64 {
        self.max_y
    }

    pub fn push(&mut self, value: f64) {
        let x = self.point_count as f64;
        self.point_count += 1;
        self.add_value((x, value));
    }

    pub fn add_value(&mut self, value: (f64, f64)) {
        let resized = self.values.push(value);

        if resized {
            self.recompute_bounds();
        } else {
            let y = value.1;
            if y < self.min_y {
                self.min_y = y;
            }
            if y > self.max_y {
                self.max_y = y;
            }
        }

        if self.min_y == self.max_y {
            // avoid zero range
            self.min_y -= 0.5;
            self.max_y += 0.5;
        }
    }

    fn recompute_bounds(&mut self) {
        self.min_y = f64::MAX;
        self.max_y = f64::MIN;

        for (_, y) in self.values.iter() {
            if *y < self.min_y {
                self.min_y = *y;
            }
            if *y > self.max_y {
                self.max_y = *y;
            }
        }
    }
}

impl LineChart for RollingLineChart {
    fn values(&self) -> &[(f64, f64)] {
        self.values()
    }

    fn min_x(&self) -> f64 {
        self.min_x()
    }

    fn max_x(&self) -> f64 {
        self.max_x()
    }

    fn min_y(&self) -> f64 {
        self.min_y()
    }

    fn max_y(&self) -> f64 {
        self.max_y()
    }

    fn color(&self) -> Color {
        self.color()
    }

    fn title(&self) -> &str {
        self.title()
    }
}

pub struct DistributionLineChart {
    title: String,
    min_y: f64,
    max_y: f64,
    values: Vec<(f64, f64)>,
    color: Color,
}

impl DistributionLineChart {
    pub fn with_color(mut self, color: Color) -> Self {
        self.color = color;
        self
    }
}

impl From<&[f32]> for DistributionLineChart {
    fn from(values: &[f32]) -> Self {
        Self::from(values.to_vec())
    }
}

impl From<Vec<f32>> for DistributionLineChart {
    fn from(values: Vec<f32>) -> Self {
        let mut chart = Self {
            title: "".to_string(),
            min_y: f64::MAX,
            max_y: f64::MIN,
            values: Vec::new(),
            color: Color::White,
        };

        for (i, value) in values.into_iter().enumerate() {
            let value = value as f64;
            chart.values.push((i as f64, value));
            if value < chart.min_y {
                chart.min_y = value;
            }
            if value > chart.max_y {
                chart.max_y = value;
            }
        }

        if chart.min_y == chart.max_y {
            // avoid zero range
            chart.min_y -= 0.5;
            chart.max_y += 0.5;
        }

        chart
    }
}

impl LineChart for DistributionLineChart {
    fn values(&self) -> &[(f64, f64)] {
        &self.values
    }

    fn min_x(&self) -> f64 {
        self.values.first().map_or(0.0, |v| v.0)
    }

    fn max_x(&self) -> f64 {
        self.values.last().map_or(0.0, |v| v.0)
    }

    fn min_y(&self) -> f64 {
        self.min_y
    }

    fn max_y(&self) -> f64 {
        self.max_y
    }

    fn color(&self) -> Color {
        self.color
    }

    fn title(&self) -> &str {
        &self.title
    }
}

#[cfg(test)]
mod tests {
    use crate::chart::RollingLineChart;

    #[test]
    fn it_works() {
        let mut chart = RollingLineChart::with_capacity(5);
        for i in 0..20 {
            chart.add_value((i as f64, i as f64 * i as f64));
            println!(
                "Added value {}, chart len: {:?}",
                i * i,
                (chart.min_y(), chart.max_y())
            );
        }
    }
}