use ratatui::style::Color;
use smallvec::SmallVec;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct ChartX(f64);
impl ChartX {
#[must_use]
#[inline]
pub const fn new(x: f64) -> Self {
Self(x)
}
#[must_use]
#[inline]
pub const fn get(self) -> f64 {
self.0
}
}
impl From<usize> for ChartX {
#[allow(clippy::cast_precision_loss)] fn from(index: usize) -> Self {
Self::new(index as f64)
}
}
impl From<f64> for ChartX {
fn from(x: f64) -> Self {
Self::new(x)
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct ChartY(f64);
impl ChartY {
#[must_use]
#[inline]
pub const fn new(y: f64) -> Self {
Self(y)
}
#[must_use]
#[inline]
pub const fn get(self) -> f64 {
self.0
}
#[must_use]
#[inline]
pub const fn max(self, other: Self) -> Self {
Self::new(self.0.max(other.0))
}
}
impl From<f64> for ChartY {
fn from(y: f64) -> Self {
Self::new(y)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ChartPoint {
pub x: ChartX,
pub y: ChartY,
}
impl ChartPoint {
#[must_use]
#[inline]
pub const fn new(x: ChartX, y: ChartY) -> Self {
Self { x, y }
}
#[must_use]
#[inline]
pub const fn as_tuple(&self) -> (f64, f64) {
(self.x.get(), self.y.get())
}
}
impl From<(f64, f64)> for ChartPoint {
fn from((x, y): (f64, f64)) -> Self {
Self::new(ChartX::new(x), ChartY::new(y))
}
}
pub type PointVec = SmallVec<[ChartPoint; 64]>;
pub type ChartDataVec = SmallVec<[BackendChartData; 8]>;
#[derive(Debug, Clone)]
pub struct BackendChartData {
pub name: String,
pub color: Color,
sent_tuples: Vec<(f64, f64)>,
recv_tuples: Vec<(f64, f64)>,
}
impl BackendChartData {
#[must_use]
pub fn new(
name: String,
color: Color,
sent_points: &[ChartPoint],
recv_points: &[ChartPoint],
) -> Self {
let sent_tuples = sent_points.iter().map(ChartPoint::as_tuple).collect();
let recv_tuples = recv_points.iter().map(ChartPoint::as_tuple).collect();
Self {
name,
color,
sent_tuples,
recv_tuples,
}
}
#[must_use]
#[inline]
pub fn sent_points_as_tuples(&self) -> &[(f64, f64)] {
&self.sent_tuples
}
#[must_use]
#[inline]
pub fn recv_points_as_tuples(&self) -> &[(f64, f64)] {
&self.recv_tuples
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_f64_eq(actual: f64, expected: f64) {
assert_eq!(actual.to_bits(), expected.to_bits());
}
fn assert_point_eq(actual: (f64, f64), expected: (f64, f64)) {
assert_f64_eq(actual.0, expected.0);
assert_f64_eq(actual.1, expected.1);
}
#[test]
fn test_chart_x() {
let x = ChartX::new(10.5);
assert_f64_eq(x.get(), 10.5);
let x2: ChartX = 5usize.into();
assert_f64_eq(x2.get(), 5.0);
}
#[test]
fn test_chart_y() {
let y1 = ChartY::new(100.0);
let y2 = ChartY::new(200.0);
assert_f64_eq(y1.max(y2).get(), 200.0);
assert_f64_eq(y2.max(y1).get(), 200.0);
}
#[test]
fn test_chart_point() {
let point = ChartPoint::new(ChartX::new(1.0), ChartY::new(2.0));
assert_point_eq(point.as_tuple(), (1.0, 2.0));
let point2: ChartPoint = (3.0, 4.0).into();
assert_f64_eq(point2.x.get(), 3.0);
assert_f64_eq(point2.y.get(), 4.0);
}
#[test]
fn test_point_vec() {
let mut points = PointVec::new();
for i in 0..60 {
points.push(ChartPoint::new(
ChartX::from(i),
ChartY::new(
f64::from(u32::try_from(i).expect("test index fits into u32")) * 1000.0,
),
));
}
assert_eq!(points.len(), 60);
assert_f64_eq(points[0].x.get(), 0.0);
assert_f64_eq(points[59].x.get(), 59.0);
}
#[test]
fn test_backend_chart_data_conversions() {
let mut sent_points = PointVec::new();
sent_points.push(ChartPoint::new(ChartX::new(0.0), ChartY::new(100.0)));
sent_points.push(ChartPoint::new(ChartX::new(1.0), ChartY::new(200.0)));
let data = BackendChartData::new(
"Test".to_string(),
Color::Green,
&sent_points,
&PointVec::new(),
);
let tuples = data.sent_points_as_tuples();
assert_eq!(tuples.len(), 2);
assert_point_eq(tuples[0], (0.0, 100.0));
assert_point_eq(tuples[1], (1.0, 200.0));
}
}