aflak_plot 0.0.3

Plotting library using imgui made for aflak
Documentation
use imgui::{ImString, ImVec2, Ui, WindowDrawList};

const TICK_COUNT: usize = 10;

const COLOR: u32 = 0xFFFFFFFF;
const TICK_SIZE: f32 = 3.0;
const LABEL_HORIZONTAL_PADDING: f32 = 2.0;

pub struct XYTicks {
    x: XTicks,
    y: YTicks,
}

pub struct XTicks {
    labels: Vec<(ImString, ImVec2)>,
    width: f32,
}

pub struct YTicks {
    labels: Vec<(ImString, ImVec2)>,
    height: f32,
}

impl XYTicks {
    pub fn prepare(ui: &Ui, xlims: (f32, f32), ylims: (f32, f32)) -> Self {
        Self {
            x: XTicks::prepare(ui, xlims),
            y: YTicks::prepare(ui, ylims),
        }
    }

    pub fn x_labels_width(&self) -> f32 {
        self.x.width()
    }

    pub fn y_labels_height(&self) -> f32 {
        self.y.height()
    }

    pub fn draw<P, S>(self, draw_list: &WindowDrawList, p: P, size: S)
    where
        P: Into<ImVec2>,
        S: Into<ImVec2>,
    {
        let p = p.into();
        let size = size.into();

        self.x.draw(draw_list, p, size);
        self.y.draw(draw_list, p, size);
    }
}

impl XTicks {
    pub fn prepare(ui: &Ui, xlims: (f32, f32)) -> Self {
        let mut width = 0.0;
        let labels = (0..=TICK_COUNT)
            .map(|i| {
                let label = ImString::new(format!(
                    "{:.0}",
                    xlims.0 + i as f32 * (xlims.1 - xlims.0) / TICK_COUNT as f32
                ));
                let text_size = ui.calc_text_size(&label, false, -1.0);
                width = text_size.x.max(width);
                (label, text_size)
            }).collect();
        XTicks { labels, width }
    }

    pub fn width(&self) -> f32 {
        self.width
    }

    pub fn draw<P, S>(self, draw_list: &WindowDrawList, p: P, size: S)
    where
        P: Into<ImVec2>,
        S: Into<ImVec2>,
    {
        let p = p.into();
        let size = size.into();

        let x_step = size.x / (self.labels.len() - 1) as f32;
        let mut x_pos = p.x;
        let y_pos = p.y + size.y;
        for (label, text_size) in self.labels {
            draw_list
                .add_line([x_pos, y_pos], [x_pos, y_pos - TICK_SIZE], COLOR)
                .build();
            draw_list.add_text([x_pos - text_size.x / 2.0, y_pos], COLOR, label.to_str());
            x_pos += x_step;
        }
    }
}

impl YTicks {
    pub fn prepare(ui: &Ui, ylims: (f32, f32)) -> Self {
        let mut height = 0.0;
        let labels = (0..=TICK_COUNT)
            .map(|i| {
                let label = ImString::new(format!(
                    "{:.0}",
                    ylims.0 + i as f32 * (ylims.1 - ylims.0) / TICK_COUNT as f32
                ));
                let text_size = ui.calc_text_size(&label, false, -1.0);
                height = (text_size.y + LABEL_HORIZONTAL_PADDING).max(height);
                (label, text_size)
            }).collect();
        YTicks { labels, height }
    }

    pub fn height(&self) -> f32 {
        self.height
    }

    pub fn draw<P, S>(self, draw_list: &WindowDrawList, p: P, size: S)
    where
        P: Into<ImVec2>,
        S: Into<ImVec2>,
    {
        let p = p.into();
        let size = size.into();

        let y_step = size.y / (self.labels.len() - 1) as f32;
        let mut y_pos = p.y + size.y;
        let x_pos = p.x;
        for (label, text_size) in self.labels {
            draw_list
                .add_line([x_pos, y_pos], [x_pos + TICK_SIZE, y_pos], COLOR)
                .build();
            draw_list.add_text(
                [
                    x_pos - text_size.x - LABEL_HORIZONTAL_PADDING,
                    y_pos - text_size.y / 2.0,
                ],
                COLOR,
                label.to_str(),
            );
            y_pos -= y_step;
        }
    }
}

pub fn add_ticks<P, S>(
    ui: &Ui,
    draw_list: &WindowDrawList,
    p: P,
    size: S,
    xlims: (f32, f32),
    ylims: (f32, f32),
) where
    P: Into<ImVec2>,
    S: Into<ImVec2>,
{
    XYTicks::prepare(ui, xlims, ylims).draw(draw_list, p, size)
}