tui_realm_stdlib/components/
sparkline.rs

1//! ## Sparkline
2//!
3//! A sparkline over more lines
4
5use tuirealm::command::{Cmd, CmdResult};
6use tuirealm::props::{
7    Alignment, AttrValue, Attribute, Borders, Color, PropPayload, PropValue, Props, Style,
8};
9use tuirealm::ratatui::{layout::Rect, widgets::Sparkline as TuiSparkline};
10use tuirealm::{Frame, MockComponent, State};
11
12// -- component
13
14/// ## Sparkline
15///
16/// A sparkline over more lines
17#[derive(Default)]
18#[must_use]
19pub struct Sparkline {
20    props: Props,
21}
22
23impl Sparkline {
24    pub fn foreground(mut self, fg: Color) -> Self {
25        self.attr(Attribute::Foreground, AttrValue::Color(fg));
26        self
27    }
28
29    pub fn background(mut self, bg: Color) -> Self {
30        self.attr(Attribute::Background, AttrValue::Color(bg));
31        self
32    }
33
34    pub fn borders(mut self, b: Borders) -> Self {
35        self.attr(Attribute::Borders, AttrValue::Borders(b));
36        self
37    }
38
39    pub fn title<S: Into<String>>(mut self, t: S, a: Alignment) -> Self {
40        self.attr(Attribute::Title, AttrValue::Title((t.into(), a)));
41        self
42    }
43
44    pub fn max_entries(mut self, max: usize) -> Self {
45        self.attr(Attribute::Width, AttrValue::Length(max));
46        self
47    }
48
49    pub fn data(mut self, data: &[u64]) -> Self {
50        self.attr(
51            Attribute::Dataset,
52            AttrValue::Payload(PropPayload::Vec(
53                data.iter().map(|x| PropValue::U64(*x)).collect(),
54            )),
55        );
56        self
57    }
58
59    /// ### data_len
60    ///
61    /// Retrieve current data len from properties
62    fn data_len(&self) -> usize {
63        self.props
64            .get(Attribute::Dataset)
65            .map_or(0, |x| x.unwrap_payload().unwrap_vec().len())
66    }
67
68    /// ### data
69    ///
70    /// Get data to be displayed, starting from provided index at `start` with a max length of `len`
71    fn get_data(&self, max: usize) -> Vec<u64> {
72        match self
73            .props
74            .get(Attribute::Dataset)
75            .map(|x| x.unwrap_payload())
76        {
77            Some(PropPayload::Vec(list)) => {
78                let mut data: Vec<u64> = Vec::with_capacity(max);
79                list.iter()
80                    .take(max)
81                    .cloned()
82                    .map(|x| x.unwrap_u64())
83                    .for_each(|x| data.push(x));
84                data
85            }
86            _ => Vec::new(),
87        }
88    }
89}
90
91impl MockComponent for Sparkline {
92    fn view(&mut self, render: &mut Frame, area: Rect) {
93        if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
94            let foreground = self
95                .props
96                .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
97                .unwrap_color();
98            let background = self
99                .props
100                .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
101                .unwrap_color();
102            let title = crate::utils::get_title_or_center(&self.props);
103            let borders = self
104                .props
105                .get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
106                .unwrap_borders();
107            let max_entries = self
108                .props
109                .get_or(Attribute::Width, AttrValue::Length(self.data_len()))
110                .unwrap_length();
111            // Get data
112            let data: Vec<u64> = self.get_data(max_entries);
113            // Create widget
114            let widget: TuiSparkline = TuiSparkline::default()
115                .block(crate::utils::get_block(borders, Some(&title), false, None))
116                .data(data.as_slice())
117                .max(max_entries as u64)
118                .style(Style::default().fg(foreground).bg(background));
119            // Render
120            render.render_widget(widget, area);
121        }
122    }
123
124    fn query(&self, attr: Attribute) -> Option<AttrValue> {
125        self.props.get(attr)
126    }
127
128    fn attr(&mut self, attr: Attribute, value: AttrValue) {
129        self.props.set(attr, value);
130    }
131
132    fn state(&self) -> State {
133        State::None
134    }
135
136    fn perform(&mut self, _cmd: Cmd) -> CmdResult {
137        CmdResult::None
138    }
139}
140
141#[cfg(test)]
142mod test {
143
144    use super::*;
145
146    use pretty_assertions::assert_eq;
147
148    #[test]
149    fn test_components_sparkline() {
150        let component = Sparkline::default()
151            .background(Color::White)
152            .foreground(Color::Black)
153            .title("bandwidth", Alignment::Center)
154            .borders(Borders::default())
155            .max_entries(8)
156            .data(&[
157                60, 80, 90, 88, 76, 101, 98, 93, 96, 102, 110, 99, 88, 75, 34, 45, 67, 102,
158            ]);
159        // Commands
160        assert_eq!(component.state(), State::None);
161        // component funcs
162        assert_eq!(component.data_len(), 18);
163        assert_eq!(component.get_data(4), vec![60, 80, 90, 88]);
164    }
165}