rserial/src/widgets/graph_view.rs
2026-02-09 04:53:44 -05:00

115 lines
3.5 KiB
Rust

use ratatui::{
buffer::Buffer,
layout::{Constraint, Layout, Rect},
style::Style,
widgets::{Block, Borders, Sparkline, Widget},
};
use crate::constants::DEFAULT_THEME;
pub struct GraphView<'a> {
float_data: &'a [f64],
int_data: &'a [i64],
}
impl<'a> GraphView<'a> {
pub fn new(float_data: &'a [f64], int_data: &'a [i64]) -> Self {
Self {
float_data,
int_data,
}
}
fn scale_floats(data: &[f64]) -> Vec<u64> {
if data.is_empty() {
return Vec::new();
}
let min = data.iter().copied().fold(f64::INFINITY, f64::min);
let max = data.iter().copied().fold(f64::NEG_INFINITY, f64::max);
let range = max - min;
if range == 0.0 {
return vec![500; data.len()];
}
data.iter()
.map(|&v| ((v - min) / range * 1000.0) as u64)
.collect()
}
fn scale_ints(data: &[i64]) -> Vec<u64> {
if data.is_empty() {
return Vec::new();
}
let min = data.iter().copied().min().unwrap();
let max = data.iter().copied().max().unwrap();
let range = max - min;
if range == 0 {
return vec![500; data.len()];
}
data.iter()
.map(|&v| ((v - min) as f64 / range as f64 * 1000.0) as u64)
.collect()
}
}
impl Widget for GraphView<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let [top, bottom] =
Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]).areas(area);
// Float graph
let float_title = if self.float_data.is_empty() {
" Float (no data) ".to_string()
} else {
let last = self.float_data.last().unwrap();
let min = self
.float_data
.iter()
.copied()
.fold(f64::INFINITY, f64::min);
let max = self
.float_data
.iter()
.copied()
.fold(f64::NEG_INFINITY, f64::max);
format!(" Float [last: {last:.2} | min: {min:.2} max: {max:.2}] ")
};
let float_block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(DEFAULT_THEME.graph_float_border))
.title(float_title);
let scaled_floats = Self::scale_floats(self.float_data);
Sparkline::default()
.block(float_block)
.data(&scaled_floats)
.max(1000)
.style(Style::default().fg(DEFAULT_THEME.graph_float_border))
.render(top, buf);
// Integer graph
let int_title = if self.int_data.is_empty() {
" Integer (no data) ".to_string()
} else {
let last = self.int_data.last().unwrap();
let min = self.int_data.iter().copied().min().unwrap();
let max = self.int_data.iter().copied().max().unwrap();
format!(" Integer [last: {last} | min: {min} max: {max}] ")
};
let int_block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(DEFAULT_THEME.graph_int_border))
.title(int_title);
let scaled_ints = Self::scale_ints(self.int_data);
Sparkline::default()
.block(int_block)
.data(&scaled_ints)
.max(1000)
.style(Style::default().fg(DEFAULT_THEME.graph_int_border))
.render(bottom, buf);
}
}