Skip to main content

altui_core/widgets/
sparkline.rs

1use crate::{
2    buffer::Buffer,
3    layout::Rect,
4    style::Style,
5    symbols,
6    widgets::{Block, Widget},
7};
8use std::cmp::min;
9
10/// Widget to render a sparkline over one or more lines.
11///
12/// # Examples
13///
14/// ```
15/// # use altui_core::widgets::{Block, Borders, Sparkline};
16/// # use altui_core::style::{Style, Color};
17/// Sparkline::default()
18///     .block(Block::default().title("Sparkline").borders(Borders::ALL))
19///     .data(&[0, 2, 3, 4, 1, 4, 10])
20///     .max(5)
21///     .style(Style::default().fg(Color::Red).bg(Color::White));
22/// ```
23#[derive(Debug, Clone)]
24pub struct Sparkline<'a> {
25    /// A block to wrap the widget in
26    block: Option<Block<'a>>,
27    /// Widget style
28    style: Style,
29    /// A slice of the data to display
30    data: &'a [u64],
31    /// The maximum value to take to compute the maximum bar height (if nothing is specified, the
32    /// widget uses the max of the dataset)
33    max: Option<u64>,
34    /// A set of bar symbols used to represent the give data
35    bar_set: symbols::bar::Set,
36}
37
38impl<'a> Default for Sparkline<'a> {
39    fn default() -> Sparkline<'a> {
40        Sparkline {
41            block: None,
42            style: Default::default(),
43            data: &[],
44            max: None,
45            bar_set: symbols::bar::NINE_LEVELS,
46        }
47    }
48}
49
50impl<'a> Sparkline<'a> {
51    pub fn block(mut self, block: Block<'a>) -> Sparkline<'a> {
52        self.block = Some(block);
53        self
54    }
55
56    pub fn style(mut self, style: Style) -> Sparkline<'a> {
57        self.style = style;
58        self
59    }
60
61    pub fn data(mut self, data: &'a [u64]) -> Sparkline<'a> {
62        self.data = data;
63        self
64    }
65
66    pub fn max(mut self, max: u64) -> Sparkline<'a> {
67        self.max = Some(max);
68        self
69    }
70
71    pub fn bar_set(mut self, bar_set: symbols::bar::Set) -> Sparkline<'a> {
72        self.bar_set = bar_set;
73        self
74    }
75}
76
77impl<'a> Widget for Sparkline<'a> {
78    fn render(&mut self, area: Rect, buf: &mut Buffer) {
79        let spark_area = match self.block.as_mut() {
80            Some(b) => {
81                let inner_area = b.inner(area);
82                b.render(area, buf);
83                inner_area
84            }
85            None => area,
86        };
87
88        if spark_area.height < 1 {
89            return;
90        }
91
92        let max = match self.max {
93            Some(v) => v,
94            None => *self.data.iter().max().unwrap_or(&1u64),
95        };
96        let max_index = min(spark_area.width as usize, self.data.len());
97        let mut data = self
98            .data
99            .iter()
100            .take(max_index)
101            .map(|e| {
102                if max != 0 {
103                    e * u64::from(spark_area.height) * 8 / max
104                } else {
105                    0
106                }
107            })
108            .collect::<Vec<u64>>();
109        for j in (0..spark_area.height).rev() {
110            for (i, d) in data.iter_mut().enumerate() {
111                let symbol = match *d {
112                    0 => self.bar_set.empty,
113                    1 => self.bar_set.one_eighth,
114                    2 => self.bar_set.one_quarter,
115                    3 => self.bar_set.three_eighths,
116                    4 => self.bar_set.half,
117                    5 => self.bar_set.five_eighths,
118                    6 => self.bar_set.three_quarters,
119                    7 => self.bar_set.seven_eighths,
120                    _ => self.bar_set.full,
121                };
122                buf.get_mut(spark_area.left() + i as u16, spark_area.top() + j)
123                    .set_symbol(symbol)
124                    .set_style(self.style);
125
126                if *d > 8 {
127                    *d -= 8;
128                } else {
129                    *d = 0;
130                }
131            }
132        }
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn it_does_not_panic_if_max_is_zero() {
142        let mut widget = Sparkline::default().data(&[0, 0, 0]);
143        let area = Rect::new(0, 0, 3, 1);
144        let mut buffer = Buffer::empty(area);
145        widget.render(area, &mut buffer);
146    }
147
148    #[test]
149    fn it_does_not_panic_if_max_is_set_to_zero() {
150        let mut widget = Sparkline::default().data(&[0, 1, 2]).max(0);
151        let area = Rect::new(0, 0, 3, 1);
152        let mut buffer = Buffer::empty(area);
153        widget.render(area, &mut buffer);
154    }
155}