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)]
18pub struct Sparkline {
19    props: Props,
20}
21
22impl Sparkline {
23    pub fn foreground(mut self, fg: Color) -> Self {
24        self.attr(Attribute::Foreground, AttrValue::Color(fg));
25        self
26    }
27
28    pub fn background(mut self, bg: Color) -> Self {
29        self.attr(Attribute::Background, AttrValue::Color(bg));
30        self
31    }
32
33    pub fn borders(mut self, b: Borders) -> Self {
34        self.attr(Attribute::Borders, AttrValue::Borders(b));
35        self
36    }
37
38    pub fn title<S: Into<String>>(mut self, t: S, a: Alignment) -> Self {
39        self.attr(Attribute::Title, AttrValue::Title((t.into(), a)));
40        self
41    }
42
43    pub fn max_entries(mut self, max: usize) -> Self {
44        self.attr(Attribute::Width, AttrValue::Length(max));
45        self
46    }
47
48    pub fn data(mut self, data: &[u64]) -> Self {
49        self.attr(
50            Attribute::Dataset,
51            AttrValue::Payload(PropPayload::Vec(
52                data.iter().map(|x| PropValue::U64(*x)).collect(),
53            )),
54        );
55        self
56    }
57
58    /// ### data_len
59    ///
60    /// Retrieve current data len from properties
61    fn data_len(&self) -> usize {
62        self.props
63            .get(Attribute::Dataset)
64            .map(|x| x.unwrap_payload().unwrap_vec().len())
65            .unwrap_or(0)
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 = self
103                .props
104                .get_or(
105                    Attribute::Title,
106                    AttrValue::Title((String::default(), Alignment::Center)),
107                )
108                .unwrap_title();
109            let borders = self
110                .props
111                .get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
112                .unwrap_borders();
113            let max_entries = self
114                .props
115                .get_or(Attribute::Width, AttrValue::Length(self.data_len()))
116                .unwrap_length();
117            // Get data
118            let data: Vec<u64> = self.get_data(max_entries);
119            // Create widget
120            let widget: TuiSparkline = TuiSparkline::default()
121                .block(crate::utils::get_block(borders, Some(title), false, None))
122                .data(data.as_slice())
123                .max(max_entries as u64)
124                .style(Style::default().fg(foreground).bg(background));
125            // Render
126            render.render_widget(widget, area);
127        }
128    }
129
130    fn query(&self, attr: Attribute) -> Option<AttrValue> {
131        self.props.get(attr)
132    }
133
134    fn attr(&mut self, attr: Attribute, value: AttrValue) {
135        self.props.set(attr, value)
136    }
137
138    fn state(&self) -> State {
139        State::None
140    }
141
142    fn perform(&mut self, _cmd: Cmd) -> CmdResult {
143        CmdResult::None
144    }
145}
146
147#[cfg(test)]
148mod test {
149
150    use super::*;
151
152    use pretty_assertions::assert_eq;
153
154    #[test]
155    fn test_components_sparkline() {
156        let component = Sparkline::default()
157            .background(Color::White)
158            .foreground(Color::Black)
159            .title("bandwidth", Alignment::Center)
160            .borders(Borders::default())
161            .max_entries(8)
162            .data(&[
163                60, 80, 90, 88, 76, 101, 98, 93, 96, 102, 110, 99, 88, 75, 34, 45, 67, 102,
164            ]);
165        // Commands
166        assert_eq!(component.state(), State::None);
167        // component funcs
168        assert_eq!(component.data_len(), 18);
169        assert_eq!(component.get_data(4), vec![60, 80, 90, 88]);
170    }
171}