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 {
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 {
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())
);
}
}
}