phink_lib/cli/ui/
chart.rs

1use chrono::DateTime;
2use ratatui::{
3    layout::{
4        Alignment,
5        Constraint,
6    },
7    prelude::Line,
8    style::{
9        Color,
10        Style,
11        Stylize,
12    },
13    symbols::Marker,
14    text::Span,
15    widgets,
16    widgets::{
17        Axis,
18        Block,
19        Chart,
20        Dataset,
21        GraphType,
22    },
23};
24use widgets::LegendPosition;
25
26pub struct ChartManager<'a> {
27    f64_array: &'a [(f64, f64)],
28}
29
30impl<'a> ChartManager<'a> {
31    pub fn new(f64_array: &'a [(f64, f64)]) -> Self {
32        Self { f64_array }
33    }
34
35    fn get_x_values(&self) -> Vec<f64> {
36        self.f64_array.iter().map(|(x, _)| *x).collect()
37    }
38
39    fn get_y_values(&self) -> Vec<f64> {
40        self.f64_array.iter().map(|(_, y)| *y).collect()
41    }
42
43    fn find_max(values: &[f64]) -> f64 {
44        values.iter().cloned().fold(f64::NEG_INFINITY, f64::max)
45    }
46
47    fn find_min(values: &[f64]) -> f64 {
48        values.iter().cloned().fold(f64::INFINITY, f64::min)
49    }
50    fn timestamp_to_str(unix_timestamp: f64) -> String {
51        DateTime::from_timestamp(unix_timestamp as i64, 0)
52            .unwrap()
53            .format("%Y-%m-%d %H:%M:%S")
54            .to_string()
55    }
56    pub fn get_first_x(&self) -> f64 {
57        let x_values = self.get_x_values();
58        Self::find_min(&x_values)
59    }
60
61    pub fn get_middle_x(&self) -> f64 {
62        let x_values = self.get_x_values();
63        let min_x = Self::find_min(&x_values);
64        let max_x = Self::find_max(&x_values);
65        (min_x + max_x) / 2.0
66    }
67
68    pub fn get_max_x(&self) -> f64 {
69        let x_values = self.get_x_values();
70        Self::find_max(&x_values)
71    }
72
73    pub fn get_first_y(&self) -> f64 {
74        let y_values = self.get_y_values();
75        Self::find_min(&y_values)
76    }
77
78    pub fn get_middle_y(&self) -> f64 {
79        let y_values = self.get_y_values();
80        let min_y = Self::find_min(&y_values);
81        let max_y = Self::find_max(&y_values);
82        (min_y + max_y) / 2.0
83    }
84
85    fn get_max_y(&self) -> f64 {
86        let y_values = self.get_y_values();
87        Self::find_max(&y_values)
88    }
89
90    pub fn create_chart(&self) -> Chart {
91        let dataset = vec![Dataset::default()
92            .marker(Marker::Braille)
93            .graph_type(GraphType::Line)
94            .style(Style::default().fg(Color::Green))
95            .data(self.f64_array)];
96
97        Chart::new(dataset)
98            .block(
99                Block::bordered().title_bottom(
100                    Line::from("Corpus evolution".bold()).alignment(Alignment::Center),
101                ),
102            )
103            .x_axis(
104                Axis::default()
105                    .title("Time")
106                    .style(Style::default().fg(Color::Gray).italic())
107                    .bounds([self.get_first_x(), self.get_max_x()])
108                    .labels([
109                        Self::timestamp_to_str(self.get_first_x()).bold(),
110                        Span::from(Self::timestamp_to_str(self.get_middle_x())),
111                        Self::timestamp_to_str(self.get_max_x()).bold(),
112                    ]),
113            )
114            .y_axis(
115                Axis::default()
116                    .title("Number of entries")
117                    .style(Style::default().fg(Color::Gray).italic())
118                    .bounds([self.get_first_y(), self.get_max_y()])
119                    .labels([
120                        self.get_first_y().to_string().bold(),
121                        Span::from(self.get_middle_y().to_string()),
122                        self.get_max_y().to_string().bold(),
123                    ]),
124            )
125            .legend_position(Some(LegendPosition::TopLeft))
126            .hidden_legend_constraints((Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)))
127    }
128}