radiate-ui 1.3.1

A Rust library for genetic algorithms and artificial evolution.
Documentation
use crate::chart::LineChart;
use ratatui::{
    buffer::Buffer,
    layout::Rect,
    style::{Color, Style, Stylize},
    symbols,
    text::Line,
    widgets::{Axis, Block, Chart, Dataset, GraphType, Widget},
};

pub struct LineChartWidget<'a, T: LineChart> {
    charts: Vec<&'a T>,
    bg_color: Color,
    show_x_axis: bool,
    show_boarders: bool,
}

impl<'a, T: LineChart> LineChartWidget<'a, T> {
    pub fn new(charts: Vec<&'a T>) -> Self {
        Self {
            charts,
            bg_color: crate::styles::ALT_BG_COLOR,
            show_x_axis: false,
            show_boarders: true,
        }
    }

    pub fn with_show_x_axis(mut self, show: bool) -> Self {
        self.show_x_axis = show;
        self
    }

    pub fn with_show_boarders(mut self, show: bool) -> Self {
        self.show_boarders = show;
        self
    }
}

impl<'a, T: LineChart> From<Vec<&'a T>> for LineChartWidget<'a, T> {
    fn from(value: Vec<&'a T>) -> Self {
        Self::new(value)
    }
}

impl<'a, T: LineChart> From<&'a T> for LineChartWidget<'a, T> {
    fn from(value: &'a T) -> Self {
        Self::new(vec![value])
    }
}

impl<'a, T: LineChart> From<Option<&'a T>> for LineChartWidget<'a, T> {
    fn from(value: Option<&'a T>) -> Self {
        match value {
            Some(chart) => Self::new(vec![chart]),
            None => Self::new(vec![]),
        }
    }
}

impl<'a, T: LineChart> From<Vec<Option<&'a T>>> for LineChartWidget<'a, T> {
    fn from(value: Vec<Option<&'a T>>) -> Self {
        let charts = value.into_iter().flatten().collect();
        Self::new(charts)
    }
}

impl<'a, T: LineChart> Widget for LineChartWidget<'a, T> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        if self.charts.is_empty() {
            let block = Block::bordered().title(Line::from(" No Data ").centered());
            block.render(area, buf);
            return;
        }

        let mut min_y = f64::MAX;
        let mut max_y = f64::MIN;
        let mut min_x = f64::MAX;
        let mut max_x = f64::MIN;

        for chart in &self.charts {
            if chart.min_y() < min_y {
                min_y = chart.min_y();
            }
            if chart.min_x() < min_x {
                min_x = chart.min_x();
            }
            if chart.max_y() > max_y {
                max_y = chart.max_y();
            }
            if chart.max_x() > max_x {
                max_x = chart.max_x();
            }
        }

        let x_bounds = (min_x, max_x);
        let y_bounds = (min_y, max_y);

        let chart = chart_widget(
            x_bounds,
            y_bounds,
            self.charts,
            self.bg_color,
            self.show_x_axis,
            self.show_boarders,
        );

        chart.render(area, buf);
    }
}

fn chart_widget<'a, T: LineChart>(
    x_bounds: (f64, f64),
    y_bounds: (f64, f64),
    charts: Vec<&'a T>,
    bg_color: Color,
    show_x_axis: bool,
    show_boarders: bool,
) -> ratatui::widgets::Chart<'a> {
    let (min_x, max_x) = x_bounds;
    let (min_y, max_y) = y_bounds;

    let mid_y = (min_y + max_y) / 2.0;
    let mid_x = (min_x + max_x) / 2.0;

    let datasets = charts
        .iter()
        .map(|dim| {
            Dataset::default()
                .marker(symbols::Marker::Braille)
                .style(Style::default().fg(dim.color()))
                .graph_type(GraphType::Line)
                .data(dim.values())
        })
        .collect::<Vec<_>>();

    let x_axis = if show_x_axis {
        Axis::default()
            .style(Style::default().gray())
            .bounds([min_x, max_x])
            .labels(Line::from(vec![
                format!("{:.2}", min_x).bold(),
                format!("{:.2}", mid_x).into(),
                format!("{:.2}", max_x).bold(),
            ]))
    } else {
        Axis::default()
            .style(Style::default().gray())
            .bounds([min_x, max_x])
    };

    let result = Chart::new(datasets).bg(bg_color).x_axis(x_axis).y_axis(
        Axis::default()
            .style(Style::default().fg(bg_color))
            .bounds([min_y, max_y])
            .labels(Line::from(vec![
                format!("{:.2}", min_y).bold(),
                format!("{:.2}", mid_y).into(),
                format!("{:.2}", max_y).bold(),
            ])),
    );

    if show_boarders {
        result.block(
            Block::bordered().title(Line::from(format!(" {} ", charts[0].title())).centered()),
        )
    } else {
        result
    }
}