Skip to main content

tui_realm_stdlib/components/
sparkline.rs

1//! A sparkline over more lines.
2
3use tuirealm::command::{Cmd, CmdResult};
4use tuirealm::component::Component;
5use tuirealm::props::{
6    AttrValue, Attribute, Borders, Color, PropPayload, PropValue, Props, QueryResult, Style, Title,
7};
8use tuirealm::ratatui::Frame;
9use tuirealm::ratatui::layout::Rect;
10use tuirealm::ratatui::widgets::Sparkline as TuiSparkline;
11use tuirealm::state::State;
12
13use crate::prop_ext::CommonProps;
14
15// -- component
16
17/// A sparkline over more lines.
18///
19/// A sparkline can be interpreted as a dense Vertical Bar Chart, without labels for each line.
20///
21/// This can be used for audio-level visualization or a type of history graph (like bandwidth, cpu usage, etc).
22#[derive(Default)]
23#[must_use]
24pub struct Sparkline {
25    common: CommonProps,
26    props: Props,
27}
28
29impl Sparkline {
30    /// Set the main foreground color. This may get overwritten by individual text styles.
31    pub fn foreground(mut self, fg: Color) -> Self {
32        self.attr(Attribute::Foreground, AttrValue::Color(fg));
33        self
34    }
35
36    /// Set the main background color. This may get overwritten by individual text styles.
37    pub fn background(mut self, bg: Color) -> Self {
38        self.attr(Attribute::Background, AttrValue::Color(bg));
39        self
40    }
41
42    /// Set the main style. This may get overwritten by individual text styles.
43    ///
44    /// This option will overwrite any previous [`foreground`](Self::foreground), [`background`](Self::background)!
45    pub fn style(mut self, style: Style) -> Self {
46        self.attr(Attribute::Style, AttrValue::Style(style));
47        self
48    }
49
50    /// Set a custom style for the border when the component is unfocused.
51    pub fn inactive(mut self, s: Style) -> Self {
52        self.attr(Attribute::UnfocusedBorderStyle, AttrValue::Style(s));
53        self
54    }
55
56    /// Add a border to the component.
57    pub fn borders(mut self, b: Borders) -> Self {
58        self.attr(Attribute::Borders, AttrValue::Borders(b));
59        self
60    }
61
62    /// Add a title to the component.
63    pub fn title<T: Into<Title>>(mut self, title: T) -> Self {
64        self.attr(Attribute::Title, AttrValue::Title(title.into()));
65        self
66    }
67
68    /// Set the max value of the bar.
69    pub fn max_entries(mut self, max: usize) -> Self {
70        self.attr(Attribute::Width, AttrValue::Length(max));
71        self
72    }
73
74    /// Set the initial data.
75    pub fn data(mut self, data: &[u64]) -> Self {
76        self.attr(
77            Attribute::Dataset,
78            AttrValue::Payload(PropPayload::Vec(
79                data.iter().map(|x| PropValue::U64(*x)).collect(),
80            )),
81        );
82        self
83    }
84
85    /// ### data_len
86    ///
87    /// Retrieve current data len from properties
88    fn data_len(&self) -> usize {
89        self.props
90            .get(Attribute::Dataset)
91            .and_then(AttrValue::as_payload)
92            .and_then(PropPayload::as_vec)
93            .map_or(0, |v| v.len())
94    }
95
96    /// ### data
97    ///
98    /// Get data to be displayed, starting from provided index at `start` with a max length of `len`
99    fn get_data(&self, max: usize) -> Vec<u64> {
100        self.props
101            .get(Attribute::Dataset)
102            .and_then(AttrValue::as_payload)
103            .and_then(PropPayload::as_vec)
104            .map(|list| {
105                list.iter()
106                    .take(max)
107                    .filter_map(PropValue::as_u64)
108                    .collect()
109            })
110            .unwrap_or_default()
111    }
112}
113
114impl Component for Sparkline {
115    fn view(&mut self, render: &mut Frame, area: Rect) {
116        if !self.common.display {
117            return;
118        }
119
120        let max_entries = self
121            .props
122            .get(Attribute::Width)
123            .and_then(AttrValue::as_length)
124            .unwrap_or(self.data_len());
125        // Get data
126        let data: Vec<u64> = self.get_data(max_entries);
127        // Create widget
128        let mut widget = TuiSparkline::default()
129            .data(data.as_slice())
130            .max(max_entries as u64)
131            .style(self.common.style);
132
133        if let Some(block) = self.common.get_block() {
134            widget = widget.block(block);
135        }
136
137        // Render
138        render.render_widget(widget, area);
139    }
140
141    fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
142        if let Some(value) = self.common.get_for_query(attr) {
143            return Some(value);
144        }
145
146        self.props.get_for_query(attr)
147    }
148
149    fn attr(&mut self, attr: Attribute, value: AttrValue) {
150        if let Some(value) = self.common.set(attr, value) {
151            self.props.set(attr, value);
152        }
153    }
154
155    fn state(&self) -> State {
156        State::None
157    }
158
159    fn perform(&mut self, cmd: Cmd) -> CmdResult {
160        CmdResult::Invalid(cmd)
161    }
162}
163
164#[cfg(test)]
165mod test {
166
167    use pretty_assertions::assert_eq;
168    use tuirealm::props::HorizontalAlignment;
169
170    use super::*;
171
172    #[test]
173    fn test_components_sparkline() {
174        let component = Sparkline::default()
175            .background(Color::White)
176            .foreground(Color::Black)
177            .title(Title::from("bandwidth").alignment(HorizontalAlignment::Center))
178            .borders(Borders::default())
179            .max_entries(8)
180            .data(&[
181                60, 80, 90, 88, 76, 101, 98, 93, 96, 102, 110, 99, 88, 75, 34, 45, 67, 102,
182            ]);
183        // Commands
184        assert_eq!(component.state(), State::None);
185        // component funcs
186        assert_eq!(component.data_len(), 18);
187        assert_eq!(component.get_data(4), vec![60, 80, 90, 88]);
188    }
189}